设计模式--模板方法模式的思考

模板方法同样也是一种很实用的方法,目的是提高代码复用,并且统一大体的算法流程,比如一个一台电脑主机,定义好放置CPU,硬盘,内存等空位后,就形成了一个骨架,那么这个就是模板,具体的CPU,内存,硬盘是什么牌子型号则不需要考虑,这些是具体到业务中的实现类所负责的事情.

模板方法模式#

模板方法模式可以说是抽象类的一种特性,可以定义抽象(abstract)方法与常规方法,抽象方法延迟到子类中实现.因此标准的模板方法一般是一个抽象类+具体的实现子类,抽象类(AbstractClass)负责整个执行流程的定义,而子类(ConcreteClass)负责某一具体流程的实现策略,类图如下:

Mybatis中的模板方法模式#

实际中由于模板方法很好的兼容性,因此经常与其他设计模式混用,并且在模板类之上增加一个接口来提高系统的灵活性.因此模板类经常作为中间层来使用,比如Mybatis的Executor的设计,其中在Executor与具体实现类之间增加中间层BaseExecutor作为模板类.

作为模板类的BaseExecutor到底做了什么呢?举一个代码比较短的例子,下面的代码是Mybatis缓存中获取不到时执行去DB查询所需要的结果,顺便再放入缓存中的流程.其中doQuery()方法便是一个抽象方法,其被延迟到子类中来实现.而缓存是所有查询都需要的功能,因此每一个查询都会去执行.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// doQuery具体查询策略延迟到子类中来实现
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

BaseExecutor作为模板类的同时其还是抽象父类,因此还可以实现一些子类锁需要的公共方法,比如事务的提交与回滚,模板类的本质还是抽象类,同时也是父类,当然可以有这些公共方法的定义.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}

@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}

总结来说模板方法类作为上级,那么其要做的事情就是针对接口提出的需求进行规划,自己实现一部分,然后把需求拆分成更加细小的任务延迟到子类中实现,这是模板的责任与目的.

Spring JDBC中的模板方法模式#

模板的另一种实现方式就是Java的接口回调机制,固定好方法模板后接收一个行为策略接口作为参数,模板中执行该接口的方法,比如Spring中的JdbcTemplate就是这样的设计.

1
2
3
4
5
6
7
8
9
10
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
...
stmt = con.createStatement();
applyStatementSettings(stmt);
// 执行传入的策略接口
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
...
}

因为篇幅原因,这里删减了很多代码,但是可以看出来这种方式实现有点策略模式的味道.其需要两个东西

  1. 方法模板,在这里是该execute()方法
  2. 策略接口,这里是StatementCallback,其本质上是一个函数是接口.

这种模式的好处自然是灵活,通过策略接口可以把行为分离出来并且可以灵活的在运行时替换掉对应的行为,雨点策略模式的味道.
那么这种到底是策略模式还是模板方法模式呢?个人认为没必要纠结这些,说他是哪个都有挺充分的理由,但是设计模式本身就是思想的体现,很多模式与模式之间都互相有思想的重叠,具体业务场景不同选择不同.

总结#

模板方法在我看来更像是一个产品经理,而接口就是需求方,面对需求方模板做的事情是制定合理的统一执行计划,然后把需求拆分成更加细小的任务,分配到对应的程序员身上.
另外模板方法模式是一种变与不变的思想体现,固定不变的,提出变化的,这样增加系统的灵活性,就像圆规画圆一样,先固定中心点,然后另一个脚随意扩展.这种思想是很实用,比如产品往往提出需求后,程序员就需要考虑具体的对象模型,那么此时比较好的做法就是尽早固定出不会变化的对象,然后其他功能在此基础上做关联来扩展,最后希望本文对你有启发.

设计模式--责任链模式的思考
工作经验--JWT在公司的实战总结