模板方法模式
源码请参考:https://github.com/GiraffePeng/design-patterns
1、定义
模板模式通常又叫模板方法模式(Template Method Pattern)是指定义一个算法的骨架,并允许子类为一个或者多个步骤提供实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤,属于行为性设计模式。
2、应用场景
模板方法模式通常适用于以下场景。
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
- 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
3、模式结构
模板方法模式包含以下主要角色。
(1) 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
抽象方法:在抽象类中申明,由具体子类实现。
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
(2) 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
4、应用实例
4.1、银行办理业务
去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
创建银行业务抽象类,其中handleBusiness为模板方法,定义了整个流程的逻辑,供客户端调用。checkGrading为钩子方法,主要用于进行整个流程逻辑的微调。
/**
* 抽象类
* 银行办理业务
*/
public abstract class BankHandleBusiness {
//模板方法
public void handleBusiness() {
//1、取号
String numberResult = offerNumber();
//2、排队
waitLine(numberResult);
//3、办理业务
String result = doHandleBusiness(numberResult);
//是否需要给业务员评分,属于钩子方法
if(checkGrading(result)) {
//4、给业务员评分
standardsGrading(result);
}
}
//具体方法
private void standardsGrading(String result) {
System.out.println(result+"成功,评分3分");
}
//具体方法
private void waitLine(String numberResult) {
System.out.println("等待被叫号......");
System.out.println("号码"+numberResult+"请到窗口办理业务");
}
//具体方法
private String offerNumber() {
String number = UUID.randomUUID().toString().substring(0,5);
System.out.println("取号的号码为" + number);
return number;
}
//钩子方法
public abstract boolean checkGrading(String result);
//抽象方法
public abstract String doHandleBusiness(String numberResult);
}
具体子类 存款类BankDeposit
/**
* 存款业务
*/
public class BankDeposit extends BankHandleBusiness{
@Override
public boolean checkGrading(String result) {
System.out.println("该"+result+"不进行评分");
return false;
}
@Override
public String doHandleBusiness(String numberResult) {
System.out.println(numberResult + "号办理存款业务");
return "存款业务";
}
}
具体子类 取款业务类BankWithdrawMoney
/**
* 取款业务
*/
public class BankWithdrawMoney extends BankHandleBusiness{
@Override
public boolean checkGrading(String result) {
System.out.println("该"+result+"进行评分");
return true;
}
@Override
public String doHandleBusiness(String numberResult) {
System.out.println(numberResult + "号办理取款业务");
return "取款业务";
}
}
创建测试类
public class BankMain {
public static void main(String[] args) {
//调用存款业务
BankHandleBusiness bankDeposit = new BankDeposit();
bankDeposit.handleBusiness();
System.out.println("----------------------------");
//调用取款业务
BankHandleBusiness bankWithdrawMoney = new BankWithdrawMoney();
bankWithdrawMoney.handleBusiness();
}
}
打印结果如下:
取号的号码为6a0ab
等待被叫号......
号码6a0ab请到窗口办理业务
6a0ab号办理存款业务
该存款业务不进行评分
----------------------------
取号的号码为95961
等待被叫号......
号码95961请到窗口办理业务
95961号办理取款业务
该取款业务进行评分
取款业务成功,评分3分
4.2、三方系统调用我方接口操作(可用于openAPI平台建设)
在三方系统调用我方接口的时候,通常会有如下几个步骤:验证数据签名是否正确、校验数据格式是否正确、解析数据并完成相应业务操作。其中验证数据签名可以交给模板父类来统一进行操作,而校验数据格式是否正确、解析数据并完成操作交给子类。
这种实现可以作为想搭建openAPI平台的基础来使用。
创建接口类,声明接受外部请求的方法
/**
* 我方系统为接收方的接口在此声明
*/
public interface ReceiveService {
final static String SIGN_KEY = "key";
/**
* 声明外部调用我方系统的接口统一入口
* @param jsonContent 报文信息
* @return
*/
public Result<Object> receiveRequest(JSONObject jsonContent);
}
模板类AbstractReceiveService.java
public abstract class AbstractReceiveService implements ReceiveService{
@Override
public Result<Object> receiveRequest(JSONObject jsonContent){
//1、校验签名是否正确
Result<Object> checkSign = checkSign(jsonContent);
if(checkSign.getCode() != 200) {
return checkSign;
}
//2、校验报文格式是否正确,如果正确并进行解析。 该操作交给具体的子类来实现
Result<Object> checkContent = checkContent(jsonContent);
if(checkContent.getCode() != 200) {
return checkContent;
}
//3、根据解析的报文数据进行DB操作。该操作交给具体的子类来实现
return doReceiveOperate();
}
//判断请求的报文是否验签通过
public Result<Object> checkSign(JSONObject dataContent){
String sign = dataContent.getString("sign");
if(sign == null || !SIGN_KEY.equals(sign)) {
return new Result<Object>(false);
}
return new Result<Object>(true);
}
public abstract Result<Object> doReceiveOperate();
public abstract Result<Object> checkContent(JSONObject jsonContent);
}
模拟两个常见的业务场景 会员和订单的子类
/**
* 会员接口具体实现
*/
public class MemberReceiveServiceImpl extends AbstractReceiveService{
private Member member;
private Result<Object> failResult = new Result<Object>(500, "部分字段缺失,请检查报文信息");
@Override
public Result<Object> doReceiveOperate() {
System.out.println("获取到的会员数据为"+member.toString());
System.out.println("进行会员业务操作流程");
return new Result<Object>("操作完成");
}
@Override
public Result<Object> checkContent(JSONObject jsonContent) {
//校验报文是否字段缺失
Long id = jsonContent.getLong("id");
String name = jsonContent.getString("name");
String phone = jsonContent.getString("phone");
if(id == null || name == null || phone == null) {
return failResult;
}
//使用建造者模式,对Member进行了改造,内部加入了建造者方法,好处在于不用在一个个的set set set。。。。。
member = Member.builder().id(id).name(name).phone(phone).build();
return new Result<Object>("解析成功");
}
}
/**
* 下单接口具体实现
*/
public class OrderReceiveServiceImpl extends AbstractReceiveService{
private Order order;
private Result<Object> failResult = new Result<Object>(500, "部分字段缺失,请检查报文信息");
@Override
public Result<Object> doReceiveOperate() {
System.out.println("获取到的订单数据为"+order.toString());
System.out.println("进行订单业务操作流程");
return new Result<Object>("操作完成");
}
@Override
public Result<Object> checkContent(JSONObject jsonContent) {
//校验报文是否字段缺失
Long id = jsonContent.getLong("id");
String orderSn = jsonContent.getString("orderSn");
BigDecimal bigDecimal = jsonContent.getBigDecimal("price");
if(id == null || orderSn == null || bigDecimal == null) {
return failResult;
}
//使用建造者模式,对Order进行了改造,内部加入了建造者方法,好处在于不用在一个个的set set set。。。。。
order = Order.builder().id(id).price(bigDecimal).orderSn(orderSn).build();
return new Result<Object>("解析成功");
}
}
创建两个实体类,Member和Order,里面有使用建造者模式来方便对象的赋值
public class Order implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private Long id;
private String orderSn;
private BigDecimal price;
public Order(Long id, String orderSn, BigDecimal price) {
super();
this.id = id;
this.orderSn = orderSn;
this.price = price;
}
public Long getId() {
return id;
}
public String getOrderSn() {
return orderSn;
}
public BigDecimal getPrice() {
return price;
}
//这里使用建造者模式,将该bean进行处理,可以更方便的赋值
static class OrderBuilder{
private Long id;
private String orderSn;
private BigDecimal price;
public OrderBuilder id(Long id) {
this.id = id;
return this;
}
public OrderBuilder orderSn(String orderSn) {
this.orderSn = orderSn;
return this;
}
public OrderBuilder price(BigDecimal price) {
this.price = price;
return this;
}
public Order build() {
return new Order(id, orderSn, price);
}
}
public static OrderBuilder builder(){
return new OrderBuilder();
}
@Override
public String toString() {
return "Order [id=" + id + ", orderSn=" + orderSn + ", price=" + price + "]";
}
}
public class Member implements Serializable{
private Long id;
private String name;
private String phone;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public Member(Long id, String name, String phone) {
super();
this.id = id;
this.name = name;
this.phone = phone;
}
static class MemberBuilder{
private Long id;
private String name;
private String phone;
public MemberBuilder id(Long id) {
this.id = id;
return this;
}
public MemberBuilder name(String name) {
this.name = name;
return this;
}
public MemberBuilder phone(String phone) {
this.phone = phone;
return this;
}
public Member build() {
return new Member(id, name, phone);
}
}
public static MemberBuilder builder() {
return new MemberBuilder();
}
@Override
public String toString() {
return "Member [id=" + id + ", name=" + name + ", phone=" + phone + "]";
}
}
封装统一的结果集Result.java
/**
* 响应信息主体
* @param <T>
*/
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
public static final int NO_LOGIN = -1;
public static final int SUCCESS = 200;
public static final int FAIL = 1;
public static final int NO_PERMISSION = 401;
private String msg = "success";
private int code = SUCCESS;
private T data;
public Result() {
super();
}
public Result(T data) {
super();
this.data = data;
}
public Result(T data, String msg) {
super();
this.data = data;
this.msg = msg;
}
public Result(Throwable e) {
super();
this.msg = e.getMessage();
this.code = FAIL;
}
public Result(int code,String msg){
this.msg = msg;
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
测试代码:
不加sign签名参数
public class Test {
public static void main(String[] args) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", 1L);
jsonObject.put("name", "测试");
jsonObject.put("phone", "手机号");
ReceiveService memberReceiveService = new MemberReceiveServiceImpl();
Result<Object> receiveRequest = memberReceiveService.receiveRequest(jsonObject);
System.out.println(receiveRequest.getCode()+receiveRequest.getMsg()+receiveRequest.getData());
}
}
执行结果:
500验签错误null
缺少参数执行结果:
500部分字段缺失,请检查报文信息null
报文数据不缺失执行结果
获取到的会员数据为Member [id=1, name=测试, phone=手机号]
进行会员业务操作流程
200success操作完成
利用模板模式,将上述代码实现,主要符合该套接口的加密和报文形式要求,都可走上述接口。
将具体接口的实现交给子类来完成,如果有新增加的接口,只需要继承AbstractReceiveService重写其抽象方法即可。
5、模板方法模式在源码中的体现
了解spring源码的同学应该知道,IOC容器的初始化方法是交给AbstractApplicationContext的refresh()方法来实现的。
该refresh()方法我的理解就是运用了模板方法模式来实现的。
下面看下源码:
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
....
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//1、调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
// 为应用上下文的刷新做准备--设置时间、记录刷新日志、初始化属性源中的占位符(事实上什么都没做)和验证必
// 要的属性等
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//2、告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从
//子类的refreshBeanFactory()方法启动
//初始化BeanFactory,并进行XML文件读取,
//这一步之后,ClassPathXmlApplicationContext实际上就已经包含了BeanFactory所提供的功能,也就是可以进行Bean的提取等基础操作了。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//3、为BeanFactory配置容器特性,例如类加载器、事件处理器等
// 准备在这个应用上下文中使用的bean factory
//对BeanFactory进行各种功能填充,@Qualifier与@Autowired这两个注解正是在这一步骤中增加的支持。
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
//4、为容器的某些子类指定特殊的BeanPost事件处理器
// bean factory 后置处理
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 调用应用上下文中作为bean注册的工厂处理器
//5、调用所有注册的BeanFactoryPostProcessor的Bean
//激活各种BeanFactory处理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//6、为BeanFactory注册BeanPost事件处理器.
//BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件
// 注册拦截创建bean的bean处理器
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
//7、初始化信息源,和国际化相关.
// 初始化消息源
initMessageSource();
// Initialize event multicaster for this context.
//8、初始化容器事件传播器.
// 初始化事件广播
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//9、调用子类的某些特殊Bean初始化方法
// 初始化特定上下文子类中的其它bean
onRefresh();
// Check for listener beans and register them.
//10、为事件传播器注册事件监听器.
// 注册监听器bean
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//11、初始化所有剩余的单例Bean
// 实例化所有的单例bean
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
//12、初始化容器的生命周期事件处理器,并发布容器的生命周期事件
// 发布相应的事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
//13、销毁已创建的Bean
destroyBeans();
// Reset 'active' flag.
//14、取消refresh操作,重置容器的同步标识。
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
//15、重设公共缓存
resetCommonCaches();
}
}
}
....
}
可以看到上述的refresh()方法也是非常的有顺序,按照事先规定好的顺序来执行IOC容器的启动与加载过程。
将一些父级公共的方法写在AbstractApplicationContext本身,将一些可变的、容易发生改变的方法交给子类来实现相应的逻辑。
6、优缺点
该模式的主要优点如下。
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
该模式的主要缺点如下。
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。