好久没写设计模式相关的文章了, 最近看源码的时候碰到一些设计模式,这里就打算将这部分抽出来放在设计模式这个系列中。
什么是模板方法模式?
为了介绍什么是模板方法模式,我们再次请出我们的实习生小陈, 为了不涉及公司的核心业务,我们这里在讲述这个故事的时候,要屏蔽一下核心业务牵扯到具体的场景, 这一天领导给小陈安排了一个这样的需求, 用代码模拟制作羊肉汤, 目前已知在制作羊肉的时候都会先清洗,但是烧制方法是不同的,根据经验来说,未来会有更多的羊肉汤来进入到我们的代码中。
小陈收到了这个需求,心里想既然清洗的逻辑是共同的的,那这个简单,我就把清洗抽出来为一个方法,然后再service里面写各自的初加工和烧制方法就可以了。于是写出的代码如下图所示:
/**
* 这是清洗方法,所有羊肉都要 清洗
*/
public interface MuttonBaseMakeService {
void waterMutton();
}
/**
* 再来一个实现类
*/
@Service
public class MuttonBaseMakeServiceImpl implements MuttonBaseMakeService{
@Override
public void waterMutton() {
System.out.println("先清洗羊肉");
}
}
/**
* 再来一个牛肉汤service
*/
public interface MuttonSoupService{
/**
* 单县羊肉汤
*/
void danXianMuttonSoup();
/**
* 河南羊肉汤
*/
void heNanMuttonSoup();
}
@Service
public class MuttonSoupServiceImpl implements MuttonSoupService{
@Autowired
private MuttonBaseMakeService muttonBaseMakeService;
@Override
public void danXianMuttonSoup() {
muttonBaseMakeService.waterMutton();
System.out.println("单县牛肉汤");
}
@Override
public void heNanMuttonSoup() {
muttonBaseMakeService.waterMutton();
System.out.println("河南牛肉汤");
}
}
感觉完成了任务, 测试也没问题之后,于是找到了领导,领导笑了笑,小陈想了想,上次指导我HttpClient怎么用的时候似乎也是这个笑容(参见HTTP Client 学习笔记 (一) 初遇篇),心里想这估计是代码写的不行。
领导清了清嗓子,然后说道: 我这次分配给你的时候是讲过未来会接入很多的羊肉的做法的,你目前的写法是每接入一种羊肉的做法都是在接口中写,然后在对应的方法写实现对吗? 小陈点了点头,说道:是的。
领导接着问道: 每接入一种羊肉汤, 都必须写一遍MuttonBaseMakeService的waterMutton方法,这是第一点。第二点还记得大学的软件工程吗? 软件工程相对于其他工程的一个特点就是变化, 其实这个需求我保留了一部分,就是其实羊肉汤制作之后,打包动作是相同的,那找你的思路是不是,每个羊肉汤方法都要加一个打包方法啊,我们公司目前准备接入二十种羊肉汤,那这改动量可不小,有没有其他方法优化一下呢?
小陈想了想,说道:是设计模式吗?
领导点了点头: 要不咱下去搜一下。
经过一番搜索小陈找到了模板方法模式,觉得很适应这个需求,模板方法模式的核心是:父类定义方法的逻辑骨架,而其中一些逻辑步骤的实现放到具体的子类中实现,这样可以做到不改变逻辑结构的前提下定义某些细节的实现。于是将代码改成了如下实现:
/**
* 父类定义逻辑骨架
*/
public abstract class MuttonSoupBase {
protected void water(){
System.out.println("清洗羊肉统一打包");
}
public void make(){
water();
muttonSoup();
pack();
}
/**
* 羊肉汤具体制作细节交给子类去实现
*/
protected abstract void muttonSoup();
protected void pack(){
System.out.println("-----打包-----");
}
}
@Service
public class DanXianMuttonSoup extends MuttonSoupBase{
@Override
protected void muttonSoup() {
System.out.println("-----单县羊肉汤------");
}
}
@Service
public class HeNanMuttonSoup extends MuttonSoupBase{
@Override
protected void muttonSoup() {
System.out.println("---- 河南羊肉汤-------");
}
}
/**
* 简单使用示例
*/
@Service
public class SoupDemo {
@Autowired
private List<MuttonSoupBase> muttonSoupBases;
public void make() {
for (MuttonSoupBase muttonSoupBase : muttonSoupBases) {
muttonSoupBase.make();
}
}
}
小陈觉得代码没问题了, 就去找到领导。领导点了点头,说道:可以,我们公司持久层用的是MyBatis,在MyBatis中用到了不少设计模式 其中就有模板方法模式,下面我带着你看一下,我们看一下MyBatis中是如何运用模板方法模式的。
MyBatis中的模板方法模式的运用
首先我们MyBatis的基础架构是这样的:
注意核心层的SQL执行,在MyBatis里面你这是一个接口,我们姑且称之为SQL执行器,定义了基本的SQL执行行为。然后BaseExecutor实现了Executor,也就是模板方法模式中的基类,定义逻辑骨架,具体的实现步骤由具体的子类来完成。如下图所示:
粗略的说BatchExecutor处理批量执行的SQL语句,批量插入、批量更新、批量删除这些,SimpleExecutor是默认的SQL执行器,每次开启或者关闭都会创建一个StatementHandler对线个,ReuseExecutor 是重用SQL执行器,在某个Statement执行过之后,会将这个Statement缓存到一个Map中。领导还打算往下讲,突然想到小陈只是一个实习生,笑了笑说道: 你注意看BaseExecutor 和 BatchExecutor、SimpleExecutor、ReuseExecutor用的就是我们上面讨论的模板方法模式,具体的查询、更新都交给对应的子类来执行,你慢慢体会一下,其实MyBatis里面模板方式用的还不少,再举一个例子StatementHandler,StatementHandler负责MyBatis和JDBC之间的交互,上面三个不同的执行器也对应三个不同的StatementHandler, MyBatis还是选择定义了一个基类BaseStatementHandler,三个子类PreparedStatementHandler、CallableStatementHandler、SimpleStatementHandler处理对应的SQL。这是很经典的实现了,小陈你可以注意体会一下。
参考资料
- 玩转 MyBatis:深度解析与定制 https://juejin.cn/book/694491...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。