参考书籍:《java设计模式》刘伟
1.模板方法模式的概念
模板方法模式,定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
2.模板方法模式的结构
- AbstractClass:实现一个模板方法,定义了算法的骨架,具体子类将重定义PrimitiveOperation以实现一个算法的步骤。AbstractClass其实就是一个抽象模板,定义并实现了一个模板方法。这个模板方法一般是一个具体的方法。它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
- ConcreteClasses:实现PrimitiveOperation以完成算法与特定子类相关的步骤。ConcreteClass实现父类所定义的一个或多个抽象方法。每一个AbstractClass都可以有任意多个ConcreteClass与之对应,而每一个ConcreteClass都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
3.模板方法模式的基本代码实现
根据上面的结构图,我们可以用代码来进行实现
AbstractClass(抽象模板):
package com.jxs.templateMethod;
public abstract class AbstractClass {
public abstract void primitiveOperation1();
public abstract void primitiveOperation2();
// 模板方法,给出了逻辑的骨架
// 而逻辑的组成是一些相应的抽象操作,他们都推迟到子类实现
public void templateMethod() {
primitiveOperation1();
primitiveOperation2();
System.out.println("");
}
}
ConcreteClass(实现父类所定义的一个或多个抽象方法)
ConcreteClassA:
package com.jxs.templateMethod;
public class ConcreteClassA extends AbstractClass {
@Override
public void primitiveOperation1() {
System.out.println("具体类A方法1实现");
}
@Override
public void primitiveOperation2() {
System.out.println("具体类A方法2实现");
}
}
ConcreteClassB:
public class ConcreteClassB extends AbstractClass {
@Override
public void primitiveOperation1() {
System.out.println("具体类B方法1实现");
}
@Override
public void primitiveOperation2() {
System.out.println("具体类B方法2实现");
}
}
客户端:
public class Client {
public static void main(String[] args) {
AbstractClass c;
c = new ConcreteClassA();
c.templateMethod();
c = new ConcreteClassB();
c.templateMethod();
}
}
运行结果:
具体类A方法1实现
具体类A方法2实现
具体类B方法1实现
具体类B方法2实现
Process finished with exit code 0
4.模板方法模式的总结
(1)模板方法模式的优点
①模板方法模式通过把不变的行为搬移到父类,去除了子类中的重复代码。
②子类实现算法的某些细节,有助于算法的扩展。
③通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
(2)模板方法模式的缺点
按照设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类负责完成具体的事务属性和方法,但是模板方式正好相反,子类执行的结果影响了父类的结果,会增加代码阅读的难度。
(3)模板方法模式适合的场景
①多个子类有共有的方法,并且逻辑基本相同。
②重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
③重构时,模板方法是一个经常使用的方法,把相同的代码抽取到父类中,然后通过构造函数约束其行为。
5. 不通过继承实现模板方法
1. 案例
Spring中的 JdbcTemplate,在用这个类时并不想去继承这个类,因为这个类的方法太多,但是我们还是想用
到 JdbcTemplate 已有的稳定的、公用的数据库连接,那么我们怎么办呢?我们可以把变化的东西抽出
来作为一个参数传入 JdbcTemplate 的方法中。但是变化的东西是一段代码,而且这段代码会用到
JdbcTemplate 中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵
JdbcTemplate 中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入
这个回调对象到 JdbcTemplate,从而完成了调用。这就是 Template Method 不需要继承的另一种实
现方式
2.案例代码
(1)模板类
定义了模板方法:executeQuery(),模板方法中的步骤是固定的,且数据库连接信息,释放连接的操作都是公用的,我们想利用,但是封装结果集的代码又是变化的。怎么办?用回调对象作为参数传入模板方法,将变化的代码写在回调函数中。
public class JdbcTemplate {
private DataSource dataSource;
public JdbcTemplate(DataSource dataSource){
this.dataSource = dataSource;
}
private Connection getConnection() throws Exception{
return this.dataSource.getConnection();
}
private PreparedStatement createPreparedStatement(Connection conn,String sql) throws Exception{
return conn.prepareStatement(sql);
}
private ResultSet executeQuery(PreparedStatement pstmt,Object [] values) throws Exception{
for (int i = 0; i <values.length; i ++){
pstmt.setObject(i,values[i]);
}
return pstmt.executeQuery();
}
private void closeStatement(Statement stmt) throws Exception{
stmt.close();
}
private void closeResultSet(ResultSet rs) throws Exception{
rs.close();
}
private void closeConnection(Connection conn) throws Exception{
//通常把它放到连接池回收
}
private List<?> parseResultSet(ResultSet rs,RowMapper rowMapper) throws Exception{
List<Object> result = new ArrayList<Object>();
int rowNum = 1;
while (rs.next()){
result.add(rowMapper.mapRow(rs,rowNum ++));
}
return result;
}
public List<?> executeQuery(String sql,RowMapper<?> rowMapper,Object [] values){
try {
//1、获取连接
Connection conn = this.getConnection();
//2、创建语句集
PreparedStatement pstmt = this.createPreparedStatement(conn,sql);
//3、执行语句集,并且获得结果集
ResultSet rs = this.executeQuery(pstmt,values);
//4、解析语句集
List<?> result = this.parseResultSet(rs,rowMapper);
//5、关闭结果集
this.closeResultSet(rs);
//6、关闭语句集
this.closeStatement(pstmt);
//7、关闭连接
this.closeConnection(conn);
return result;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
(2)定义封装结果集的接口
生成回调对象
public interface RowMapper<T> {
//回调函数
public T mapRow(ResultSet rs, int rowNum) throws Exception;
}
(3) 调用模板方法
public class MemberDao {
//为什么不继承,主要是为了解耦
private JdbcTemplate JdbcTemplate = new JdbcTemplate(null);
public List<?> query(){
String sql = "select * from t_member";
return JdbcTemplate.executeQuery(sql,new RowMapper<Member>(){
//回调函数
@Override
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);
}
}