闲聊设计模式 | 模板方法模式
本文介绍设计模式之模板方法模式.
一、前言
Template Pattern 模板方法 来自 Wiki 百科的介绍:
模板方法模型是一种行为设计模型。模板方法是一个定义在父类别的方法,在模板方法中会呼叫多个定义在父类别的其他方法,而这些方法有可能只是抽象方法并没有实作,模板方法仅决定这些抽象方法的执行顺序,这些抽象方法的实作由子类别负责,并且子类别不允许覆写模板方法。
模板方法是属于设计模式的行为型模式
模板方法模式按照我的白话文理解:
首先定一个“抽象类”,它有一个模板方法A,定义可能需要子类实现的方法B,C,D…,然后在A方法里面编排好了B,C,D等的位置,当实现类调用A的时候,会调用各自实现的B,C,D方法(某些方法有默认实现,子类可以不实现),从而在一样的大流程里面进行不一样的操作。
二、简单实例
故事背景
阳光明媚的一天,玩码部落来了一群腿长一米八的MM,它们来自台湾,杭州及北京,她们将介绍各自家乡是如何准备丰富的晚餐的。
2.1 Java版本
2.1.1 定义接口
1public interface Dinner {
2
3 /**
4 * 晚餐
5 */
6 void doDinner();
7
8}
定义一个接口,就一个无参的 void
方法。
2.1.2 定义抽象方法
1public abstract class AbstractDinner implements Dinner {
2
3 protected String name;
4
5 public AbstractDinner(String name) {
6 this.name = name;
7 }
8
9 private void eat() {
10 System.out.printf("%sMM说:开吃喽", name).println();
11 }
12
13 protected boolean foodEnough() {
14 return true;
15 }
16
17 protected void doShopping() {
18 System.out.println("门口小贩买菜");
19 }
20
21 protected abstract void beforeCooking();
22
23 protected abstract String doCooking();
24
25 protected abstract void afterCooking();
26
27 @Override
28 public void doDinner() {
29 if (!foodEnough()) {
30 doShopping();
31 }
32
33 beforeCooking();
34 System.out.println(doCooking());
35 afterCooking();
36
37 eat();
38 }
39
40}
定义 AbstractDinner
实现接口,它自身有五个方法,默认实现的 foodEnough
和 doShopping
,以及抽象方法 beforeCooking
、doCooking
和 afterCooking
。
doDinner
编排了这些方法的流程或者说定义了各阶段的步骤。
2.1.3 定义实现类
1public class BeijingDinner extends AbstractDinner {
2
3 public BeijingDinner(String name) {
4 super(name);
5 }
6
7 @Override
8 protected void beforeCooking() {
9 System.out.printf("%sMM 在洗菜切菜", name).println();
10 }
11
12 @Override
13 protected String doCooking() {
14 return name + "MM 在做" + name + "菜";
15 }
16
17 @Override
18 protected void afterCooking() {
19 System.out.printf("%sMM 让你去品尝", name).println();
20 }
21
22}
23
24public class TaiwanDinner extends AbstractDinner {
25
26 public TaiwanDinner(String name) {
27 super(name);
28 }
29
30 @Override
31 protected boolean foodEnough() {
32 // 每次都买食物
33 return false;
34 }
35
36 @Override
37 protected void doShopping() {
38 System.out.println("生鲜超市购买,一定要买茶叶蛋");
39 }
40
41 @Override
42 protected void beforeCooking() {
43 System.out.printf("%sMM 在洗菜切菜", name).println();
44 }
45
46 @Override
47 protected String doCooking() {
48 return name + "MM 在做" + name + "菜";
49 }
50
51 @Override
52 protected void afterCooking() {
53 System.out.printf("%sMM 让你去品尝", name).println();
54 }
55
56}
57
58public class HangzhouDinner extends AbstractDinner {
59
60 public HangzhouDinner(String name) {
61 super(name);
62 }
63
64 @Override
65 protected boolean foodEnough() {
66 // 每次都买食物
67 return false;
68 }
69
70 @Override
71 protected void beforeCooking() {
72 System.out.printf("%sMM 在洗菜切菜", name).println();
73 }
74
75 @Override
76 protected String doCooking() {
77 return name + "MM 在做" + name + "菜";
78 }
79
80 @Override
81 protected void afterCooking() {
82 System.out.printf("%sMM 让你去品尝", name).println();
83 }
84
85}
定义了三个实现类,都实现了3个抽象方法。另外 TaiwanDinner
重写了另外两个方法,HangzhouDinner
只重写了 foodEnough
。
2.1.4 运行例子
代码:
1public class DinnerDemo {
2
3 public static void main(String[] args) {
4 System.out.println("---准备台湾餐---");
5 Dinner dinner1 = new TaiwanDinner();
6 dinner1.doDinner();
7 System.out.println("---准备杭州餐---");
8 Dinner dinner2 = new HangzhouDinner();
9 dinner2.doDinner();
10 System.out.println("---准备北京餐---");
11 Dinner dinner3 = new BeijingDinner();
12 dinner3.doDinner();
13 }
14
15}
输出结果:
1---准备台湾餐---
2生鲜超市购买,一定要买茶叶蛋
3台湾MM 在洗菜切菜
4台湾MM 在做台湾菜
5台湾MM 让你去品尝
6台湾MM说:开吃喽
7---准备杭州餐---
8门口小贩买菜
9杭州MM 在洗菜切菜
10杭州MM 在做杭州菜
11杭州MM 让你去品尝
12杭州MM说:开吃喽
13---准备北京餐---
14北京MM 在洗菜切菜
15北京MM 在做北京菜
16北京MM 让你去品尝
17北京MM说:开吃喽
2.2 Golang 版本
声明下:在 golang 中,由于不存在抽象类和真正的继承,所以只能通过一个基础类来充当抽象类,子类通过组合基础类来实现通用方法的继承。
2.2.1 定义接口
1type Dinner interface {
2 DoDinner()
3}
2.2.2 定义抽象类
1type AbstractCooking struct {
2 foodEnough func() bool
3 doShopping func()
4 beforeCooking func()
5 doCooking func() string
6 afterCooking func()
7 Name string
8}
9
10func (d *AbstractCooking) DoDinner() {
11 if !d.foodEnough() {
12 d.doShopping()
13 }
14 d.beforeCooking()
15 fmt.Println(d.doCooking())
16 d.afterCooking()
17 d.eat()
18}
19
20func (d *AbstractCooking) eat() {
21 fmt.Println(fmt.Sprintf("%sMM说:开吃喽", d.Name))
22}
这里和 Java 不一样的地方是 go 的结构体可以拥有 func() 属性(也可以拥有接口属性)。
实现 Dinner
的方法 DoDinner
, 编排了一系列的方法。
2.2.3 定义实现类
1type HZDinner struct {
2 AbstractCooking
3}
4
5func NewHZDinner(name string) *HZDinner {
6 c := new(HZDinner)
7 c.Name = name
8 // 选择实现的
9 c.AbstractCooking.foodEnough = c.foodEnough
10 c.AbstractCooking.doShopping = doShopping
11 // 必须实现的
12 c.AbstractCooking.beforeCooking = c.beforeCooking
13 c.AbstractCooking.doCooking = c.doCooking
14 c.AbstractCooking.afterCooking = c.afterCooking
15 return c
16}
17
18func (c *HZDinner) foodEnough() bool {
19 return false
20}
21
22func (c *HZDinner) beforeCooking() {
23 println(fmt.Printf("%sMM 在洗菜切菜", c.Name))
24}
25
26func (c *HZDinner) doCooking() string {
27 return fmt.Sprintf("%sMM 在做%s菜", c.Name, c.Name)
28}
29
30func (c *HZDinner) afterCooking() {
31 println(fmt.Printf("%sMM 让你去品尝", c.Name))
32}
33
34type TWDinner struct {
35 AbstractCooking
36}
37
38func NewTWDinner(name string) *TWDinner {
39 c := new(TWDinner)
40 c.Name = name
41 // 选择实现的
42 c.AbstractCooking.foodEnough = c.foodEnough
43 c.AbstractCooking.doShopping = c.doShopping
44 // 必须实现的
45 c.AbstractCooking.beforeCooking = c.beforeCooking
46 c.AbstractCooking.doCooking = c.doCooking
47 c.AbstractCooking.afterCooking = c.afterCooking
48 return c
49}
50
51func (c *TWDinner) foodEnough() bool {
52 return false
53}
54
55func (c *TWDinner) doShopping() {
56 fmt.Println("生鲜超市购买,一定要买茶叶蛋")
57}
58
59func (c *TWDinner) beforeCooking() {
60 println(fmt.Printf("%sMM 在洗菜切菜", c.Name))
61}
62
63func (c *TWDinner) doCooking() string {
64 return fmt.Sprintf("%sMM 在做%s菜", c.Name, c.Name)
65}
66
67func (c *TWDinner) afterCooking() {
68 println(fmt.Printf("%sMM 让你去品尝", c.Name))
69}
70
71type BJDinner struct {
72 AbstractCooking
73}
74
75func NewBJDinner(name string) *BJDinner {
76 c := new(BJDinner)
77 c.Name = name
78 // 选择实现的
79 c.AbstractCooking.foodEnough = foodEnough
80 c.AbstractCooking.doShopping = doShopping
81 // 必须实现的
82 c.AbstractCooking.beforeCooking = c.beforeCooking
83 c.AbstractCooking.doCooking = c.doCooking
84 c.AbstractCooking.afterCooking = c.afterCooking
85 return c
86}
87
88func (c *BJDinner) beforeCooking() {
89 println(fmt.Printf("%sMM 在洗菜切菜", c.Name))
90}
91
92func (c *BJDinner) doCooking() string {
93 return fmt.Sprintf("%sMM 在做%s菜", c.Name, c.Name)
94}
95
96func (c *BJDinner) afterCooking() {
97 println(fmt.Printf("%sMM 让你去品尝", c.Name))
98}
2.2.4 定义默认实现方法
1func foodEnough() bool {
2 return true
3}
4
5func doShopping() {
6 fmt.Println("门口小贩买菜")
7}
为什么有独立的默认方法,因为 struct 里面定义了 foodEnough() bool
和 doShopping()
两个方法,go 里面是不能重名的,因此不能再写属于 AbstractCooking
的方法。
1func (d *AbstractCooking) foodEnough() bool {
2 return true
3}
这个方法如何写了,去掉了 struct 的 foodEnough() bool
,那么创建实现类的时候就没办法 c.AbstractCooking.foodEnough = c.foodEnough
进行 func() 赋值,从而 d.foodEnough()
会一直调用 AbstractCooking
下的 foodEnough()
,实现类没办法自定义实现了。
2.2.5 运行例子
代码:
1func TestTemplate1(t *testing.T) {
2 fmt.Println("---准备台湾餐---")
3 d1 := NewTWDinner("台湾")
4 d1.DoDinner()
5 fmt.Println("---准备杭州餐---")
6 d2 := NewHZDinner("杭州")
7 d2.DoDinner()
8 fmt.Println("---准备北京餐---")
9 d3 := NewBJDinner("北京")
10 d3.DoDinner()
11}
输出结果:
1---准备台湾餐---
2生鲜超市购买,一定要买茶叶蛋
3台湾MM 在洗菜切菜
4台湾MM 在做台湾菜
5台湾MM 让你去品尝
6台湾MM说:开吃喽
7---准备杭州餐---
8门口小贩买菜
9杭州MM 在洗菜切菜
10杭州MM 在做杭州菜
11杭州MM 让你去品尝
12杭州MM说:开吃喽
13---准备北京餐---
14北京MM 在洗菜切菜
15北京MM 在做北京菜
16北京MM 让你去品尝
17北京MM说:开吃喽
2.3 Golang版本2
上面例子是在 struct 中定义,相当于是抽象方法的意思,而这版本把那部分方法都定义到了接口。
2.3.1 定义接口
1type Dinner2 interface {
2 foodEnough() bool
3 doShopping()
4 beforeCooking()
5 doCooking() string
6 afterCooking()
7}
2.3.2 定义抽象类
1type AbstractDinner struct {
2}
3
4func (AbstractDinner) foodEnough() bool {
5 return true
6}
7
8func (AbstractDinner) doShopping() {
9 fmt.Println("门口小贩买菜")
10}
11
12func (AbstractDinner) beforeCooking() {
13}
14
15func (AbstractDinner) doCooking() string {
16 return ""
17}
18
19func (AbstractDinner) afterCooking() {
20}
实现 Dinner2
接口,和下面等价
1type AbstractDinner struct {
2 Dinner2
3}
4
5func (AbstractDinner) foodEnough() bool {
6 return true
7}
8
9func (AbstractDinner) doShopping() {
10 fmt.Println("门口小贩买菜")
11}
2.3.3 定义实现类
1type HangzhouDinner struct {
2 AbstractDinner
3}
4
5func NewHangzhouDinner(name string) Dinner2 {
6 return &HangzhouDinner{
7 AbstractDinner{
8 Name: name,
9 },
10 }
11}
12
13func (d *HangzhouDinner) foodEnough() bool {
14 return false
15}
16
17func (d *HangzhouDinner) beforeCooking() {
18 fmt.Println(fmt.Sprintf("%sMM 在洗菜切菜", d.Name))
19}
20
21func (d *HangzhouDinner) doCooking() string {
22 return fmt.Sprintf("%sMM 在做%s菜", d.Name, d.Name)
23}
24
25func (d *HangzhouDinner) afterCooking() {
26 fmt.Println(fmt.Sprintf("%sMM 让你去品尝", d.Name))
27}
28
29type BeijingDinner struct {
30 AbstractDinner
31}
32
33func NewBeijingDinner(name string) Dinner2 {
34 return &BeijingDinner{
35 AbstractDinner{
36 Name: name,
37 },
38 }
39}
40
41func (d *BeijingDinner) beforeCooking() {
42 fmt.Println(fmt.Sprintf("%sMM 在洗菜切菜", d.Name))
43}
44
45func (d *BeijingDinner) doCooking() string {
46 return fmt.Sprintf("%sMM 在做%s菜", d.Name, d.Name)
47}
48
49func (d *BeijingDinner) afterCooking() {
50 fmt.Println(fmt.Sprintf("%sMM 让你去品尝", d.Name))
51}
52
53type TaiwanDinner struct {
54 AbstractDinner
55}
56
57func NewTaiwanDinner(name string) Dinner2 {
58 return &TaiwanDinner{
59 AbstractDinner{
60 Name: name,
61 },
62 }
63}
64
65func (d *TaiwanDinner) foodEnough() bool {
66 return false
67}
68
69func (d *TaiwanDinner) doShopping() {
70 fmt.Println("生鲜超市购买,一定要买茶叶蛋")
71}
72
73func (d *TaiwanDinner) beforeCooking() {
74 fmt.Println(fmt.Sprintf("%sMM 在洗菜切菜", d.Name))
75}
76
77func (d *TaiwanDinner) doCooking() string {
78 return fmt.Sprintf("%sMM 在做%s菜", d.Name, d.Name)
79}
80
81func (d *TaiwanDinner) afterCooking() {
82 fmt.Println(fmt.Sprintf("%sMM 让你去品尝", d.Name))
83}
2.3.4 定义模板方法
1func DoDinner(d Dinner2) {
2 if !d.foodEnough() {
3 d.doShopping()
4 }
5
6 d.beforeCooking()
7 fmt.Println(d.doCooking())
8 d.afterCooking()
9 d.eat()
10}
11
12func (ad AbstractDinner) eat() {
13 fmt.Println(fmt.Sprintf("%sMM说:开吃喽", ad.Name))
14}
如果想把抽象方法放到结构体上,也可以如下:
1type Dinner2 interface {
2 ...
3 DoDinner(d Dinner2)
4}
5
6func (ad AbstractDinner) DoDinner(d Dinner2) {
7 if !d.foodEnough() {
8 d.doShopping()
9 }
10
11 d.beforeCooking()
12 fmt.Println(d.doCooking())
13 d.afterCooking()
14 ad.eat()
15}
16
17func (ad AbstractDinner) eat() {
18 fmt.Println(fmt.Sprintf("%sMM说:开吃喽", ad.Name))
19}
到时候调用的话就 DoDinner 改成 d1 := NewTaiwanDinner("台湾") d1.DoDinner(d1)
。
2.3.5 运行例子
代码:
1func TestTemplate2(t *testing.T) {
2 fmt.Println("---准备台湾餐---")
3 d1 := NewTaiwanDinner("台湾")
4 DoDinner(d1)
5 fmt.Println("---准备杭州餐---")
6 d2 := NewHangzhouDinner("杭州")
7 DoDinner(d2)
8 fmt.Println("---准备北京餐---")
9 d3 := NewTaiwanDinner("北京")
10 DoDinner(d3)
11}
输出结果:
1---准备台湾餐---
2生鲜超市购买,一定要买茶叶蛋
3台湾MM 在洗菜切菜
4台湾MM 在做台湾菜
5台湾MM 让你去品尝
6台湾MM说:开吃喽
7---准备杭州餐---
8门口小贩买菜
9杭州MM 在洗菜切菜
10杭州MM 在做杭州菜
11杭州MM 让你去品尝
12杭州MM说:开吃喽
13---准备北京餐---
14生鲜超市购买,一定要买茶叶蛋
15北京MM 在洗菜切菜
16北京MM 在做北京菜
17北京MM 让你去品尝
18北京MM说:开吃喽
2.4、例子说明
三、开源框架使用场景
列举某几个框架,供大家参考
3.1 JDK AbstractList
1public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
2 ...
3 public void add(int index, E element) {
4 throw new UnsupportedOperationException();
5 }
6
7 ...
8 public boolean addAll(int index, Collection<? extends E> c) {
9 rangeCheckForAdd(index);
10 boolean modified = false;
11 for (E e : c) {
12 add(index++, e);
13 modified = true;
14 }
15 return modified;
16 }
17 ...
18}
实现类实现 add 的逻辑,如:
1
2public class ArrayList<E> extends AbstractList<E>
3 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
4{
5 // ...
6 public void add(int index, E element) {
7 rangeCheckForAdd(index);
8
9 ensureCapacityInternal(size + 1); // Increments modCount!!
10 System.arraycopy(elementData, index, elementData, index + 1,
11 size - index);
12 elementData[index] = element;
13 size++;
14 }
15 // ...
16}
17
3.1 spring 中的 事务管理器
1public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
2
3 // ...
4 public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
5 Object transaction = doGetTransaction();
6
7 // Cache debug flag to avoid repeated checks.
8 boolean debugEnabled = logger.isDebugEnabled();
9
10 if (definition == null) {
11 // Use defaults if no transaction definition given.
12 definition = new DefaultTransactionDefinition();
13 }
14
15 if (isExistingTransaction(transaction)) {
16 // Existing transaction found -> check propagation behavior to find out how to behave.
17 return handleExistingTransaction(definition, transaction, debugEnabled);
18 }
19
20 // Check definition settings for new transaction.
21 if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
22 throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
23 }
24
25 // No existing transaction found -> check propagation behavior to find out how to proceed.
26 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
27 throw new IllegalTransactionStateException(
28 "No existing transaction found for transaction marked with propagation 'mandatory'");
29 }
30 else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
31 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
32 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
33 SuspendedResourcesHolder suspendedResources = suspend(null);
34 if (debugEnabled) {
35 logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
36 }
37 try {
38 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
39 DefaultTransactionStatus status = newTransactionStatus(
40 definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
41 doBegin(transaction, definition);
42 prepareSynchronization(status, definition);
43 return status;
44 }
45 catch (RuntimeException ex) {
46 resume(null, suspendedResources);
47 throw ex;
48 }
49 catch (Error err) {
50 resume(null, suspendedResources);
51 throw err;
52 }
53 }
54 else {
55 // Create "empty" transaction: no actual transaction, but potentially synchronization.
56 if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
57 logger.warn("Custom isolation level specified but no actual transaction initiated; " +
58 "isolation level will effectively be ignored: " + definition);
59 }
60 boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
61 return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
62 }
63 }
64 // ...
65
66 protected abstract void doBegin(Object transaction, TransactionDefinition definition)
67 throws TransactionException;
68
69 ...
70}
这里只拿 getTransaction
和 doBegin
举例,非常标准的写法 getTransaction
还用了 final 描述,表示子类不允许改变。
当我自定义事务管理器的时候,比如每次事务开启创建一个 traceId,效果如下:
1public class GongDaoDataSourceTransactionManager extends DataSourceTransactionManager implements
2 ResourceTransactionManager, InitializingBean, EnvironmentAware {
3
4 // ...
5 protected void doBegin(Object transaction, TransactionDefinition definition) {
6 String currentXid;
7 if (XIDContext.getCurrent() != null && null != XIDContext.getCurrent().getId()) {
8 currentXid = xidGenerator.getXID();
9 XIDContext.childCurrent(new TransactionContent(currentXid, definition.getName()));
10 } else {
11 currentXid = xidGenerator.getXID();
12 XIDContext.setXid(new TransactionContent(currentXid, definition.getName()));
13 }
14
15 if (null == HyjalTransactionFileAppender.contextHolder.get()) {
16 String logFileName = HyjalTransactionFileAppender.makeLogFileName(new Date(),
17 HyjalTransactionFileAppender.logId.get());
18 HyjalTransactionFileAppender.contextHolder.set(logFileName);
19 }
20
21 try {
22 if (null == XIDContext.getCurrentXid()) {
23 XIDContext.setXid(xidGenerator.getXID());
24 }
25 super.doBegin(transaction, definition);
26 } finally {
27 if (logger.isDebugEnabled()) {
28 logger.debug("do begin Xid : {}", currentXid);
29 HyjalTransactionLogger.log("do begin Xid : {}, obj : {}", currentXid, definition.getName());
30 }
31 }
32 }
33 // ...
34}
欢迎各位补充更多的例子和场景
四、优势和劣势
4.1 优势
- 对扩展开放,对修改关闭,符合“开闭原则”
- 定义标准算法,子类可自定义扩展,把变性和不变性分离
- 子类的扩展不会导致标准算法结构
- 能够提高代码复用,公共部分易维护
4.2 劣势
- 每一个实现类都需要定义自己的行为,如果复杂业务实现类会膨胀的比较多
五、参考
https://www.tutorialspoint.com/design_pattern/template_pattern.htm