模板方法模式
定义:定义在一个操作中的一个算法框架,把一些步骤推迟到子类去实现。模板方法模式让子类不需要改变算法结构而重新定义特定的算法步骤。
也就是说模板方法定义了一系列算法步骤,子类可以去实现/覆盖其中某些步骤,但不能改变这些步骤的执行步骤。
模板方法有如下功能:
- 能解决代码冗余问题。
- 把某些算法步骤延迟到子类,子类可以根据不同情况改变/实现这些方法,而子类的新方法不会引起以有父类的功能变化。
- 易于扩展。我们通过创建新类,实现可定制化的方法就可以扩展功能。此例中,可以扩展坐船回家。
- 父类提供了算法的框架,控制方法执行流程,而子类不能改变算法流程,子类方法的调用由父类模板方法决定。执行步骤的顺序有时候非常重要,我们在容器加载和初始化资源时,为避免子类执行错误的顺序,经常使用该模式限定子类方法的调用次序。比如此例中,你不能先回家再买票,也不能先庆祝在回家。
- 父类可以把那些重要的、不允许改变的方法屏蔽掉,不让子类去覆写(Override/Overwrite)它们,比如在 Java语言中,我们声明这些方法为 final 或者 private 即可。
回家过年问题:
package com.guilin.moshi;
//模板模式(Template Method)
public abstract class Guochunjie {
// 模板方法(框架方法):按照一定顺序执行的逻辑
public void celebrateSpringFestival() {
maipiao();
huijia();
guonian();
}
// 买票
protected final void maipiao() {
System.out.println("买票...");
}
// 回家(抽象方法)
protected abstract void huijia();
// 过年
protected final void guonian() {
System.out.println("过年...");
}
}
class ByFeiji extends Guochunjie {
@Override
protected void huijia() {
System.out.println("坐飞机回家...");
}
}
class ByHuoche extends Guochunjie {
@Override
protected void huijia() {
System.out.println("坐火车回家...");
}
}
// 测试
public class Test {
public static void main(String[] args) {
Guochunjie byFeiji = new ByFeiji();
Guochunjie byHuoche = new ByHuoche();
byFeiji.celebrateSpringFestival();
byHuoche.celebrateSpringFestival();
}
模板方法模式的应用很广泛,但过分地使用模板方法模式,往往会引起子类的泛滥。为了解决此问题,通常我们结合使用回调来处理这种问题。
引入回调(Callback)
回调表示一段可执行的逻辑的引用(或者指针),我们把该引用(或者指针)传递到另外一段逻辑(或者方法)里供这段逻辑适时调用。
回调在不同语言有不同的实现,例如,在 C语言里经常使用函数指针实现回调,在 C#语言里使用代理实现,而在 Java语言里使用内部匿名类实现回调。
查询数据库里的记录问题:
package com.guilin.moshi;
import java.sql.Connection;
import java.sql.ResultSet;
class SimpleJdbcQueryTemplate{
public <T> T query(String queryString, ResultSetHandler<T> rsHandler) {
//...
try {
// 获得一个数据库的连接
Connection connection = getCollection ();
//创建一个 PreparedStatement实例
stmt = connection.prepareStatement(queryString);
//执行该查询并返回结果
ResultSet rs = stmt .executeQuery();
//调用回调 rsHandler 的 handle(ResultSet rs )方法来处理查询结果并返回
return rsHandler.handle(rs);
} catch ( SQLException ex) {
//...
} finally {
//...
}
}
}
public void testTemplate() {
boolean called = new SimpleJdbcQueryTemplate().query( "select * from EMP where NAME = 'GUILIN'" ,
new ResultSetHandler<Boolean>() {
@ Override
public Boolean handle(ResultSet rs) {
// TODO Auto-generated method stub
return Boolean.TRUE;
}
}
);
}
import java.sql.Connection;
import java.sql.ResultSet;
//泛型
public interface ResultSetHandler<T> {
public T handle(ResultSet rs);
}
public T handle(ResultSet rs);
}
class SimpleJdbcQueryTemplate{
public <T> T query(String queryString, ResultSetHandler<T> rsHandler) {
//...
try {
// 获得一个数据库的连接
Connection connection = getCollection ();
//创建一个 PreparedStatement实例
stmt = connection.prepareStatement(queryString);
//执行该查询并返回结果
ResultSet rs = stmt .executeQuery();
//调用回调 rsHandler 的 handle(ResultSet rs )方法来处理查询结果并返回
return rsHandler.handle(rs);
} catch ( SQLException ex) {
//...
} finally {
//...
}
}
}
public void testTemplate() {
boolean called = new SimpleJdbcQueryTemplate().query( "select * from EMP where NAME = 'GUILIN'" ,
new ResultSetHandler<Boolean>() {
@ Override
public Boolean handle(ResultSet rs) {
// TODO Auto-generated method stub
return Boolean.TRUE;
}
}
);
}
我们使用了 Java的匿名类来回调类处理查询结果。这样即使有1千种不同的查询,也不需要增加一个专门的文件。
此模式在 Spring框架里使用十分广泛,关于 ORM框架和 Jdbc框架的源码。
总结:
为了解决回家过年的问题,使用了模版方法模式。模版方法模式可以解决某些场景种的代码冗余问题,但也可能引入了类的泛滥问题,结合使用回调避免类的泛滥。同时应该注意如果回调实现的接口较多,代码较为复杂时,会引起阅读问题。