设计模式 | 七、模板方法模式[TemplateMethodPattern]

模板方法模式

源码请参考: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、优缺点

该模式的主要优点如下。

  • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  • 它在父类中提取了公共的部分代码,便于代码复用。
  • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

该模式的主要缺点如下。

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值