[Java]设计模式之模板方法模式

模板方法模板定义

模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。

模板方法模式的使用场景

  • 算法或操作遵循相似的逻辑

  • 重构时(把相同的代码抽取到父类中)

  • 重要、复杂的算法,核心算法设计为模板算法

模板方法的例子

我们去银行取钱的时候通常会有以下流程:

  1. 取号
  2. 填写单据
  3. 等待叫号
  4. 窗口办理

每个人都是通过这个模板(流程)进行办理业务,接下来用代码来实践一下。

类图

模板方法模式uml图

代码实现

银行的模板方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.doity.templatemethod;

/**
* Created by Right on 2016/5/8.
*/
public abstract class Bank {

/**
* 银行业务流程的模板方法
* 封装了所有子类共同遵循的算法框架
*/
public final void bankTemplate() {
// 1、取号、
getTicket();
// 2、填写单据
fillDocuments();
// 3、等待叫号
attentionTheCalling();
// 4、办理业务
handleBusiness();
}


/**
* 基本方法,取号
*/
protected void getTicket() {
System.out.println("在银行门口取号");
}

/**
* 抽象的基本方法,填写表格
*/
protected abstract void fillDocuments();

/**
* 基本方法,等待叫号
*/
protected void attentionTheCalling() {
System.out.println("等待叫号");
}

/**
* 抽象的基本方法,办理业务
*/
protected abstract void handleBusiness();


}

具体的子类,取钱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.doity.templatemethod;

/**
* Created by Right on 2016/5/8.
*
* 具体的子类,取钱。
*/
public class WithdrawMoney extends Bank {
@Override
protected void fillDocuments() {
System.out.println("填表内容:取10000元人民币");
}

@Override
protected void handleBusiness() {
System.out.println("呼叫XX号");
System.out.println("柜台工作人员:这是您的10000元");
}
}

具体的子类,开户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.doity.templatemethod;

/**
* Created by Right on 2016/5/8.
*
* 具体的子类,开户
*/

public class OpenAccount extends Bank {
@Override
protected void fillDocuments() {
System.out.println("填表中,我是XXX,家在BB,收入AA");
}

@Override
protected void handleBusiness() {
System.out.println("叫号成功");
System.out.println("恭喜XXX先生你开户成功!");
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.doity.templatemethod;

/**
* Created by Right on 2016/5/8.
*
* 模板方法模板的测试类
*/
public class TemplateMethodTest {

public static void main(String[] args) {

WithdrawMoney withdrawMoney = new WithdrawMoney();
withdrawMoney.bankTemplate();

System.out.println("----------分割线1-------------");

OpenAccount openAccount = new OpenAccount();
openAccount.bankTemplate();
}
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
在银行门口取号
填表内容:取10000元人民币
等待叫号
呼叫XX号
柜台工作人员:这是您的10000元
----------分割线1-------------
在银行门口取号
填表中,我是XXX,家在BB,收入AA
等待叫号
叫号成功
恭喜XXX先生你开户成功!

以上是最简单虚拟的场景,假设有不需要填表的业务:存钱。

钩子函数使子类选择挂载

修改一下Bank的类

1
2
3
4
//          2、填写单据
if (isillDocuments()){
fillDocuments();
}
1
2
3
4
5
6
7
8
/*
* Hook, 钩子函数,提供一个默认或空的实现
* 具体的子类可以自行决定是否挂钩以及如何挂钩
* 部分业务不需要填表,可以选择
*/
protected boolean isillDocuments() {
return true;
}

测试类结果

1
2
3
System.out.println("----------分割线2-------------");
ToDeposit toDeposit = new ToDeposit();
toDeposit.bankTemplate();

使用钩子函数的测试结果

1
2
3
4
5
6
7
8
9
10
在银行门口取号
填表中,我是XXX,家在BB,收入AA
等待叫号
叫号成功
恭喜XXX先生你开户成功!
----------分割线2-------------
在银行门口取号
等待叫号
叫号成功
存款成功

结论

方法模板的总结

准备一个抽象类,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法交由子类实现剩余逻辑,用钩子方法给予更大的灵活性。最后将方法汇总成一个不可以改变的模板方法。

模板方法模板的优点

  • 封装性好
  • 复用性好
  • 屏蔽细节
  • 便于维护

模板方法模式的缺点

Java 是一种单继承的语言。也就是一个类,只能继承一个类。

我们设想在一个系统当中大量的使用了继承,如果我们需要重构,通过模板方法的模式来抽取共性,以及增加算法或者叫做增加架构的弹性的时候,因为我们的类已经处于了继承层次某个结构之中,再通过模板方法引入新的继承的时候,也许会遇到困难。

参考资料