3

搞懂钩子方法和模板方法,看完这篇就够了

 2 years ago
source link: https://segmentfault.com/a/1190000040922681
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文节选自《设计模式就该这样学》

通常的模板方法模式中会设计一个abstract的抽象方法,交给它的子类实现,这个方法称为模板方法。而钩子方法,是对于抽象方法或者接口中定义的方法的一个空实现,也是模板方法模式的一种实现方式。

1 模板方法模式中的钩子方法

我们以网络课程创建流程为例:发布预习资料 → 制作课件PPT → 在线直播 → 提交课堂笔记 → 提交源码 → 布置作业 → 检查作业。首先创建AbastractCourse抽象类。

public abstract class AbastractCourse {
    
    public final void createCourse(){
        //1.发布预习资料
        postPreResoucse();
        
        //2.制作课件PPT
        createPPT();
        
        //3.在线直播
        liveVideo();
        
        //4.上传课后资料
        postResource();
        
        //5.布置作业
        postHomework();
        
        if(needCheckHomework()){
            checkHomework();
        }
    }

    protected abstract void checkHomework();

    //钩子方法
    protected boolean needCheckHomework(){return  false;}

    protected void postHomework(){
        System.out.println("布置作业");
    }

    protected void postResource(){
        System.out.println("上传课后资料");
    }

    protected void liveVideo(){
        System.out.println("直播授课");
    }

    protected void createPPT(){
        System.out.println("制作课件");
    }

    protected void postPreResoucse(){
        System.out.println("发布预习资料");
    }

}

上面代码中有个钩子方法,可能有些小伙伴不是太理解,在此笔者稍做解释。设计钩子方法的主要目的是干预执行流程,使得控制行为流程更加灵活,更符合实际业务的需求。钩子方法的返回值一般为适合条件分支语句的返回值(如boolean、int等)。小伙伴们可以根据自己的业务场景决定是否需要使用钩子方法。
然后创建JavaCourse类。

public class JavaCourse extends AbastractCourse {
    private boolean needCheckHomework = false;

    public void setNeedCheckHomework(boolean needCheckHomework) {
        this.needCheckHomework = needCheckHomework;
    }

    @Override
    protected boolean needCheckHomework() {
        return this.needCheckHomework;
    }

    protected void checkHomework() {
        System.out.println("检查Java作业");
    }
}

创建PythonCourse类。

public class PythonCourse extends AbastractCourse {
    protected void checkHomework() {
        System.out.println("检查Python作业");
    }
}

最后编写客户端测试代码。

    public static void main(String[] args) {
        System.out.println("=========架构师课程=========");
        JavaCourse java = new JavaCourse();
        java.setNeedCheckHomework(false);
        java.createCourse();


        System.out.println("=========Python课程=========");
        PythonCourse python = new PythonCourse();
        python.createCourse();
    }
        

通过这样一个案例,相信小伙伴们对模板方法模式有了一个基本的印象。为了加深理解,我们结合一个常见的业务场景进行介绍。

2 使用模板方法模式重构JDBC业务操作

创建一个模板类JdbcTemplate,封装所有的JDBC操作。以查询为例,每次查询的表都不同,返回的数据结构也就都不一样。我们针对不同的数据,都要封装成不同的实体对象。而每个实体封装的逻辑都是不一样的,但封装前和封装后的处理流程是不变的,因此,可以使用模板方法模式设计这样的业务场景。首先创建约束ORM逻辑的接口RowMapper。

/**
 * ORM映射定制化的接口
 * Created by Tom.
 */
public interface RowMapper<T> {
    T mapRow(ResultSet rs,int rowNum) throws Exception;
}

然后创建封装了所有处理流程的抽象类JdbcTemplate。

public abstract class JdbcTemplate {
    private DataSource dataSource;

    public JdbcTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public final List<?> executeQuery(String sql,RowMapper<?> rowMapper,Object[] values){
        try {
            //1.获取连接
            Connection conn = this.getConnection();
            //2.创建语句集
            PreparedStatement pstm = this.createPrepareStatement(conn,sql);
            //3.执行语句集
            ResultSet rs = this.executeQuery(pstm,values);
            //4.处理结果集
            List<?> result = this.parseResultSet(rs,rowMapper);
            //5.关闭结果集
            rs.close();
            //6.关闭语句集
            pstm.close();
            //7.关闭连接
            conn.close();
            return result;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private List<?> parseResultSet(ResultSet rs, RowMapper<?> rowMapper) throws Exception {
        List<Object> result = new ArrayList<Object>();
        int rowNum = 0;
        while (rs.next()){
            result.add(rowMapper.mapRow(rs,rowNum++));
        }
        return result;
    }


    private ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            pstm.setObject(i,values[i]);
        }
        return pstm.executeQuery();
    }

    private PreparedStatement createPrepareStatement(Connection conn, String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }

    private Connection getConnection() throws SQLException {
        return this.dataSource.getConnection();
    }
}

创建实体对象Member类。

public class Member {

    private String username;
    private String password;
    private String nickname;
    private int age;
    private String addr;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }
}

创建数据库操作类MemberDao。

public class MemberDao extends JdbcTemplate {
    public MemberDao(DataSource dataSource) {
        super(dataSource);
    }

    public List<?> selectAll(){
        String sql = "select * from t_member";
        return super.executeQuery(sql, new RowMapper<Member>() {
            public Member mapRow(ResultSet rs, int rowNum) throws Exception {
                Member member = new Member();
                //字段过多,原型模式
                member.setUsername(rs.getString("username"));
                member.setPassword(rs.getString("password"));
                member.setAge(rs.getInt("age"));
                member.setAddr(rs.getString("addr"));
                return member;
            }
        },null);
    }
}

最后编写客户端测试代码。

public static void main(String[] args) {
        MemberDao memberDao = new MemberDao(null);
        List<?> result = memberDao.selectAll();
        System.out.println(result);
}

希望通过这两个案例的业务场景分析,小伙伴们能够对模板方法模式有更深的理解。

【推荐】Tom弹架构:收藏本文,相当于收藏一本“设计模式”的书

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom弹架构 』可获取更多技术干货!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK