模板方法模式学习笔记

2 篇文章 0 订阅
1 篇文章 0 订阅

模板方法模式定义:定义一个操作中算法的骨架,将一些步骤延迟到子类中实现,使得子类可以不改变一个算法的结构,即可重定义此算法的某些特定步骤。

模板方法模式其实很简单,很多人在不知不觉中可能已经使用过此设计模式。下面的例子源于最常见的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将这些重复又必不可少的方法都放到了模板中。

这就是基于组合的模板设计模式的实际使用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值