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

>>最全面的Java面试大纲及答案解析(建议收藏)  

导语

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

背景

在详细介绍模板方法模式之前,我们先引入一个背景进行说明。相信大家都使用过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的源码进行学习。


关于模板方法模式的介绍就到这里了,如果觉得这篇文章对你有帮助可以扫描下方二维码关注本公众号或者分享到朋友圈让更多的人看到。

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

此外,沉思君建了一个技术交流群,感兴趣的朋友可以扫描下方二维码参与到群聊中来。后期会不定期分享一些学习心得跟学习资料。

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

如果加不进群聊,可以扫描下方二维码,加沉思君为好友,备注“加群”,沉思君手动拉你进群。

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