模板方法模式定义:定义一个操作中算法的骨架,将一些步骤延迟到子类中实现,使得子类可以不改变一个算法的结构,即可重定义此算法的某些特定步骤。
模板方法模式其实很简单,很多人在不知不觉中可能已经使用过此设计模式。下面的例子源于最常见的dao层数据库操作,每次数据库操作的打开连接,事务控制,关闭链接等等是必不可少的操作,为了提高代码的重用性,用木板设计模式是非常合适的。
模板设计模式的实现,有两种方式:基于继承的模板设计模式和基于组合的模板设计模式。
基于继承的模板设计模式
1.基于继承的模板方法模式,主要有三个要点:
1.1)模板方法:一些特定的、共用的操作处理,提取到抽象父类中,统一定义。模板方法一般定义为private方法,以完成内部封装。若某些模板方法,需要在其他类中单独调用,可以设置为public方法,但一般用final修饰,目的也是保护模板方法不能修改。
1.2)特殊方法:特殊方法由子类实现,也叫钩子函数,这些针对不同逻辑抛出的钩子函数,由子类实现以控制算法步骤的特殊性。一般设置为protected访问权限,并且为设置为抽象方法。
1.3)流程方法:流程方法为模板方法模式的主要方法,此方法中完成算法的顺序调用。访问权限为public。2.下面是使用示例
2.1)首先,建立一个抽象模板类AbstractInheritDao,类中将模板方法抽象出来,放在一个类中,以供调用:
package template.inherit;
/**
* 基于继承实现模板方法模式
* 模板抽象类,模拟数据库新增
* 此示例有三个主要部分:1.模板方法;2.特殊步骤;3.流程方法
*/
public abstract class AbstractInheritDao {
/**
* 1.模板方法
* openConnection()/closeConnection()为公共的模板方法
* 模板方法一般定义为私有方法,隐藏其在子类中的访问
*/
private void openConnection(){
System.out.println("以开启数据库连接");
}
private void closeConnection(){
System.out.println("已关闭数据库连接");
}
/**
* 2.特殊步骤
* 示例为添加操作,定义为抽象方法,延迟到子类中实现。
* 并且一般限制为,只有子类才可以访问。
*/
protected abstract void addOperation();
/**
* 3.流程方法
* 模板的主方法,控制算法的流程
*/
public void execute(){
openConnection();
addOperation(); //完成数据新增
closeConnection();
}
}
2.2)建立业务类,这些类业务流程与模板中的流程相同,但是可以对特殊步骤的处理方式自定义(abstract方法在子类中可以重写)。下面例举两个业务类:
package template.inherit;
/**
* 账户Dao,继承模板方法模式抽象类
*/
public class AccountDao extends AbstractInheritDao {
@Override
protected void addOperation() {
System.out.println("AccountDao 添加了账号:快钱账号添加测试");
}
}
package template.inherit;
/**
* 用户Dao,继承模板方法模式抽象类
*/
public class UserDao extends AbstractInheritDao {
@Override
protected void addOperation() {
System.out.println("UserDao 添加了用户:测试人员");
}
}
2.3)junit测试:
package template.test;
import org.junit.Test;
import template.combinaton.TemplateCombinDao;
import template.combinaton.RoleDao;
import template.inherit.AbstractInheritDao;
import template.inherit.AccountDao;
import template.inherit.UserDao;
public class TemplateTest {
@Test
public void testTemplateInherit(){
AbstractInheritDao userDao = new UserDao();
userDao.execute();
System.out.println("----------------------------");
AbstractInheritDao accountDao = new AccountDao();
accountDao.execute();
}
}
2.4)输出结果:
已开启数据库连接
UserDao 添加了用户:测试人员
已关闭数据库连接
----------------------------
已开启数据库连接
AccountDao 添加了账号:快钱账号添加测试
已关闭数据库连接
2.5)小结:
示例很简单,模拟数据添加,将数据库的开启和关闭用模板方法来完成,而具体要添加的数据用特殊方法定义,抛给子类来实现,用以完成特殊性的处理。如果我们想更多的控制一个流程的算法,只需要增加钩子函数,给子类增加控制点即可。如我们是否需要打印日志,可以增加一个钩子函数:protected abstract Boolean isPrintLog();子类通过此方法控制是否打印日志即可。
上面的示例有明显的缺点:对于一个Dao操作,我们要完成的功能不仅仅是添加,还有删除、修改等等。而基于继承的模板设计模式只有一个流程方法,这显然不能满足需求。基于此,我们可以使用组合模板设计模式。
基于组合的模板设计模式
1.组合模板的设计模式有四个要点:
1.1)模板方法:与继承实现的模板设计模式功能的模板方法相同,封装通用的模板方法。
1.2)流程方法:与继承实现的模板设计模式功能的流程方法相同,封装算法的流程。但在方法中会传入钩子函数,钩子函数在特殊方法中定义,然后再流程方法的特定位置发挥作用。这里一般会用一个接口来定义此钩子函数。
1.3)特殊方法:与继承实现的模板设计模式有所不同,这里的特殊方法可以定义多个。特殊方法(如add)负责调用流程方法(execute),并在流程方法中回调自定义的钩子函数(invoCallback),完成整个操作过程。客户端已不再直接调用流程方法,而是通过调用特殊方法,完成流程方法的调用。
1.4)钩子函数:钩子函数只是一个简单接口中的一个抽象方法,在特殊方法中自定义,以满足不同操作的需求。
2.下面是使用示例
2.1)首先定义钩子函数,一个简单的接口和抽象方法,以备参数传递之用。
package template.combinaton;
public interface CbtCallback {
public void invoCallBack();
}
2.2)其次定义一个模板,注意这个模板只是一个普通类,这意味着,所有方法的定义均在模板中完成:
package template.combinaton;
/**
* 基于组合实现模板方法模式
* 模板抽象类,模拟数据库增、删、改、查
* 此示例有三个主要部分:1.模板方法;2.流程方法;3.特殊方法;4.钩子函数
*/
public class TemplateCombinDao {
/**
* 1.模板方法
* openConnection()/closeConnection()为公共的模板方法
* 模板方法一般定义为私有方法,隐藏其在子类中的访问
*/
private void openConnection(){
System.out.println("已开启数据库连接");
}
private void closeConnection(){
System.out.println("已关闭数据库连接");
}
/**
* 2.流程方法
* 此方法由回调函数调用,不再由客户端调用
*/
private void execute(CbtCallback call){
openConnection();
call.invoCallBack();
closeConnection();
}
/**
* 3.特殊方法
* 将所有要实现的方法(增删改查)都创建在模板中,调用execute流程方法时,传入自定义钩子方法invoCallBack
* 客户端要完成特定的功能,就调用特定的方法(增删改查),而不再直接调用execute方法
*/
public void add(final String desc){
execute(new CbtCallback(){<span style="white-space:pre"> </span>//4.自定义钩子函数,传入流程函数,在流程函数中被调用
public void invoCallBack() {
System.out.println("add something:"+desc);
}
});
}
public void modify(final String desc){
execute(new CbtCallback(){
public void invoCallBack() {
System.out.println("modify something:"+desc);
}
});
}
public void remove(){
//as before,omit..
}
public void find(){
//ad before,omit..
}
}
2.3)建立业务类,业务类与模板不再是继承的关系,只是组合关系。业务类通过调用特殊方法,完成需要的逻辑处理。这里例举一个角色的dao:
package template.combinaton;
public class RoleDao {
//基于组合,实现模板设计模式
TemplateCombinDao templateDao = new TemplateCombinDao();
public void add(){
templateDao.add("角色添加");
}
public void modify(){
templateDao.add("角色修改");
}
public void remove(){
templateDao.add("角色删除");
}
public void find(){
templateDao.add("角色查询");
}
}
2.4)junit测试:
package template.test;
import org.junit.Test;
import template.combinaton.TemplateCombinDao;
import template.combinaton.RoleDao;
public class TemplateTest {
@Test
public void testTemplateCombin(){
RoleDao roleDao = new RoleDao();
roleDao.add();
System.out.println("-----------------------------");
roleDao.modify();
}
}
2.5)输出结果:
已开启数据库连接
add something:角色添加
已关闭数据库连接
-----------------------------
已开启数据库连接
add something:角色修改
已关闭数据库连接
2.6)组合模板模式小结:
继承与组合模板设计模式的区别与联系:
a)二者都要提取封装模板方法和流程方法;
b)继承中的流程方法只有一个(其实可以有多个流程方法,但这会造成多个流程的模板方法重复,不能算最好的实现),只能完成单一的功能;组合中的流程方法只有一个,但可以将所有需要的功能都定义在模板中,可以完成多个功能。
c)从命名上区分:继承是抽象类,组合是实际类。并且在调用时区别很大。
d)客户端调用模板设计模式,实现操作时:前者是调用继承类的流程方法;而后者是调用组合类的特殊方法。
基于组合的模板设计模式,也是Spring中JDBCTemplate的实现方式,其将数据库的基本操作封装成模板方法,在流程方法中调用。然后再将所有可能用到的方法定义在模板中,通过自定义钩子函数,调用流程方法。
Spring JDBCTemplate设计模式浅析
如上所述,JDBCTemplate也是使用基于组合的模板设计模式来实现的。下面是代码片段,对照上面的示例,很容易理解。
1.首先是一个钩子函数StatementCallback:
package org.springframework.jdbc.core;
import java.sql.SQLException;
import java.sql.Statement;
import org.springframework.dao.DataAccessException;
public interface StatementCallback<T> {
T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}
2.其次是模板JDBCTemplate,模板中定义了很多几个流程方法和很多特殊方法(增删改查)。下面是代码片段:
package template.combinaton;
public class JDBCTemplate {
// ......
public <T> T execute(StatementCallback<T> action)
throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null
&& this.nativeJdbcExtractor
.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
T result = action.doInStatement(stmtToUse);
handleWarnings(stmt);
return result;
} catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool
// deadlock
// in the case when the exception translator hasn't been initialized
// yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback",
getSql(action), ex);
} finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
// ....
@Override
public <T> T query(final String sql, final ResultSetExtractor<T> rse)
throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query [" + sql + "]");
}
class QueryStatementCallback implements StatementCallback<T>,
SqlProvider {
@Override
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
return rse.extractData(rsToUse);
} finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
// ......
}
上面的query()方法就是一个查询的特殊方法,在方法中自定义了钩子函数。在方法的最后,其调用execute方法,并传入QueryStatmentCallBack参数,将钩子函数传入流程函数,使其在execute方法中起作用,最终达到共用模板的目的。
在execute()方法中有doInStatement方法的回调。当然,此方法是流程方法,你可以看到方法的开始和结束都是模板方法:getConnection()->createStatement()->..doInStatement..->releaseResource()->transaction()->colseConnection。除去doInStatement之外,全都是模板方法,Spring将这些重复又必不可少的方法都放到了模板中。
这就是基于组合的模板设计模式的实际使用。