聊聊设计模式之模板方法模式

导语

模板方法模式是指在父类中定义好算法的骨架,而把具体的算法步骤交给子类去实现的一种设计模式。模板方式模式可以在不改变算法整体骨架的情况下,对算法的某些步骤进行定制或者对算法的某些步骤进行复用。

背景

在详细介绍模板方法模式之前,我们先引入一个背景进行说明。相信大家都使用过JDBC操作过关系型数据库,我们先回忆一下使用JDBC的大致步骤是什么。

  1. 首先,我们需要先创建connection,或者从连接池中获取connection。
  2. 其次,需要创建statement。
  3. 接着,执行数据库查询,获取resultSet。
  4. 然后,将resultSet装换成业务bean。
  5. 最后,关闭resultSet、statement与connection,并处理各种异常。

大家可以看到,如果使用JDBC操作数据库的话,即使是一个简简单单的数据库查询,也需要经过上述几个步骤,且缺一不可。那么有没有办法对上面的某些步骤进行复用呢?大家再仔细地观察上述JDBC操作的5个步骤,除了第4个步骤是根据具体的数据库查询而定制的之外,其他步骤对于不同的数据库查询而言都是一模一样的。因此我们可以想办法将除了第4个步骤之外的步骤“抽出来”进行复用,第4个步骤就让不同的数据库查询去“定制”就好了,这也就是模板方法的精髓:父类将一个算法的步骤定义好,将共同的步骤在父类实现以实现复用,而子类只要实现定制化的部分就行了。其好处是一来可以实现代码复用,二来可以在不改变算法结构的前提下自定义算法的某些步骤。

使用模板方法模式

接下来,我们用模板方法模式实现一个简单的JDBC查询模板。

我们先定义好父类,也就是JDBC查询的模板。由于需要子类实现从resultSet到业务bean的转换,所以将父类定义成抽象类:

public abstract class QueryTemplate<T> {

    private static final String URL="jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8";

    private static final String USER="root";   
 
    private static final String PASSWORD="root";

    protected Connection getConnection(){
        try{
            return DriverManager.getConnection(URL,USER,PASSWORD);
        }catch (Exception e){
            throw  new RuntimeException("Get connection error.",e);
        }
    }    

    
    protected Statement createStatement(Connection connection){
        try {
            return  connection.createStatement();
        }catch (Exception e){
            throw new RuntimeException("Create statement error.",e);
        }
    }    

     protected ResultSet executeQuery(Statement statement,String sql){        
         try {
            return statement.executeQuery(sql);
        }catch (Exception e){            
             throw new RuntimeException("Execute query error.",e);
        }

    }    

     protected void cleanResource(ResultSet resultSet,Statement statement,Connection connection){        
     try {            
            if(resultSet!=null){
                resultSet.close();
            }            

             if(statement!=null){
                statement.close();
            }            

             if(connection!=null){
                connection.close();
            }
        }catch (Exception e){            
             throw new RuntimeException("Clear resource error.",e);
        }
    }    

     public T query(final String sql){
        Connection connection=null;
        Statement statement=null;
        ResultSet resultSet=null;        
         try {
            connection = getConnection();
            statement = createStatement(connection);
            resultSet = executeQuery(statement, sql);            
            return resolveResultSet(resultSet);
        }finally {
            cleanResource(resultSet,statement,connection);
        }

    }   

      protected abstract T  resolveResultSet(ResultSet resultSet);

}

我们把JDBC的几个步骤分别定义在不同的方法里面,由于获取connection、创建statement、执行查询获取resultSet和清理资源是每个业务查询都共用的,因此我们在QueryTemplate中进行实现,这样的话子类就能对这些操作进行复用了,而对于将resultSet转化成业务bean的操作我们定义了一个抽象方法resolveResultSet,子类只要实现resolveResultSet方法即可根据具体的需求将resultSet转换成业务bean了。

接着,我们再写一个继承自QueryTemplate的子类UserQueryTemplate:

public class UserQueryTemplate extends QueryTemplate<User> {    

    @Override
    protected User resolveResultSet(ResultSet resultSet) {
        try {
            if(resultSet.next()){
                long id = resultSet.getLong("id");
                String name = resultSet.getString("name");
                User user=new User();
                user.setId(id);
                user.setName(name);                
                return user;
            }
        }catch (Exception e){            
                throw new RuntimeException("ResolveResultSet error.",e);
        }        
         return null;
    }
}

子类UserQueryTemplate非常简单,只要实现resolveResultSet方法就行了,该方法就是将resultSet转换成一个User对象。

接下来我们写一个客户端测试下:

public class Client {    

    public static void main(String[] args) {
        QueryTemplate<User> queryTemplate=new UserQueryTemplate();
        User user = queryTemplate.query("select * from user where id =1");
        System.out.println(user);

    }
}

结果输出如下:

至此,我们就使用模板方法模式实现了一个JDBC查询模板。如果有其他的业务查询,只要继承QueryTemplate类并实现抽象方法resolveResultSet即可,从此以后我们就不用接触底层的JDBC操作了,而只要专注于如何将resultSet转换成业务bean就行了,这就是模板方法模式的威力!

模板方法模式的改进

在上述例子中,想必大家都见识到模板方法模式的优点了,但是上述模板方法模式的实现有一个局限就是模板是定义在一个抽象类中的,子类必须继承父类才能对模板的某些步骤进行定制,而由于Java中只能单继承,这意味着子类无法再继承其他类,这是一个缺点。既然Java只能单继承,那么我们使用模板的时候不要使用继承不就行了?如果你使用的是JDK8或JDK9,那么可以将模板声明为接口,复用的算法步骤使用default进行声明,这样的话子类只要实现父接口,然后实现自定义的算法步骤就行了,如此一来子类就能继承其他类了。如果你使用的是JDK8以下,那么可以将定制化的算法步骤作为一个回调函数“传递”给模板类,这样一来就不用继承模板类了,实际上Spring的JdbcTemplate就是使用回调函数的方式实现模板方法模式的,感兴趣的同学可以去阅读Spring JdbcTemplate的源码进行学习。

关于模板方法模式的介绍就到这里了。

原文发布于微信公众号 - 编程沉思录(code-thinker)

原文发表时间:2018-02-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏blackpiglet

Go 语言反射和范型在 API 服务中的应用

  API 接口自然是要获取传过来的数据,不同接口要获取的数据自然也不一样,如果不做特殊处理,必然是每个接口都有一堆功能重复的从 request 里获取参数的代...

8020
来自专栏MasiMaro 的技术博文

OLEDB 调用存储过程

除了常规调用sql语句和进行简单的插入删除操作外,OLEDB还提供了调用存储过程的功能,存储过程就好像是用SQL语句写成的一个函数,可以有参数,有返回值。 ...

11410
来自专栏古时的风筝

SharePoint—用REST方式访问列表

REST的定义与作用                                                                      ...

22050
来自专栏钟绍威的专栏

初学File类

对File类的基本方法的理解 今天刚开始学了File类 一开始看思想编程看得迷迷糊糊的,之后受不了了,直接去看API文档 归纳: File->jav...

202100
来自专栏hbbliyong

c++/c 获取cpp文件行号跟文件名

编译器内置宏: 先介绍几个编译器内置的宏定义,这些宏定义不仅可以帮助我们完成跨平台的源码编写,灵活使用也可以巧妙地帮我们输出非常有用的调试信息。 ANSI C标...

29470
来自专栏企鹅号快讯

MyBatis之Mapper XML 文件详解(一)

MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进...

23150
来自专栏编码前线

设计模式之单例模式

单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。他有很多好处,能够避免实例对象的重复创建,减少创建实例的系统开销,节省内...

12330
来自专栏余林丰

单例模式

单例模式,顾名思义,在程序运行时有且仅有一个实例存在。最常见的一个应用场景就是网站访问量的计数器,试想如果允许创建多个实例,那还怎么计数,这个时候就得创建有且仅...

20750
来自专栏xingoo, 一个梦想做发明家的程序员

Elasticsearch 连接查询

在一般的关系型数据库中,都支持连接操作。 在ES这种分布式方案中进行连接操作,代价是十分昂贵的。 不过ES也提供了相类似的操作,支持水平任意扩展,实现连接...

349100
来自专栏Java后端生活

JDBC(四)JDBC元数据和反射的利用

Java 通过JDBC获得连接以后,得到一个Connection 对象,可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据...

7320

扫码关注云+社区

领取腾讯云代金券