什么是模板方法模式?
为了介绍什么是模板方法模式,我们再次请出我们的实习生小陈, 为了不涉及公司的核心业务,我们这里在讲述这个故事的时候,要屏蔽一下核心业务牵扯到具体的场景, 这一天领导给小陈安排了一个这样的需求, 用代码模拟制作羊肉汤, 目前已知在制作羊肉的时候都会先清洗,但是烧制方法是不同的,根据经验来说,未来会有更多的羊肉汤来进入到我们的代码中。
小陈收到了这个需求,心里想既然清洗的逻辑是共同的的,那这个简单,我就把清洗抽出来为一个方法,然后再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。这是很经典的实现了,小陈你可以注意体会一下。