https://refactoringguru.cn/design-patterns/template-method

行为模式-模板方法模式

亦称: Template Method

意图

模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。

问题

假如你正在开发一款分析公司文档的数据挖掘程序。 用户需要向程序输入各种格式 (PDF、 DOC 或 CSV) 的文档, 程序则会试图从这些文件中抽取有意义的数据, 并以统一的格式将其返回给用户。

该程序的首个版本仅支持 DOC 文件。 在接下来的一个版本中, 程序能够支持 CSV 文件。 一个月后, 你 “教会” 了程序从 PDF 文件中抽取数据。

数据挖掘类中包含许多重复代码。

一段时间后, 你发现这三个类中包含许多相似代码。 尽管这些类处理不同数据格式的代码完全不同, 但数据处理和分析的代码却几乎完全一样。 如果能在保持算法结构完整的情况下去除重复代码, 这难道不是一件很棒的事情吗?

还有另一个与使用这些类的客户端代码相关的问题: 客户端代码中包含许多条件语句, 以根据不同的处理对象类型选择合适的处理过程。 如果所有处理数据的类都拥有相同的接口或基类, 那么你就可以去除客户端代码中的条件语句, 转而使用多态机制来在处理对象上调用函数。

解决方案

模板方法模式建议将算法分解为一系列步骤, 然后将这些步骤改写为方法, 最后在 “模板方法” 中依次调用这些方法。 步骤可以是 抽象的, 也可以有一些默认的实现。 为了能够使用算法, 客户端需要自行提供子类并实现所有的抽象步骤。 如有必要还需重写一些步骤 (但这一步中不包括模板方法自身)。

让我们考虑如何在数据挖掘应用中实现上述方案。 我们可为图中的三个解析算法创建一个基类, 该类将定义调用了一系列不同文档处理步骤的模板方法。

模板方法将算法分解为步骤, 并允许子类重写这些步骤, 而非重写实际的模板方法。

首先, 我们将所有步骤声明为 抽象类型, 强制要求子类自行实现这些方法。 在我们的例子中, 子类中已有所有必要的实现, 因此我们只需调整这些方法的签名, 使之与超类的方法匹配即可。

现在, 让我们看看如何去除重复代码。 对于不同的数据格式, 打开和关闭文件以及抽取和解析数据的代码都不同, 因此无需修改这些方法。 但分析原始数据和生成报告等其他步骤的实现方式非常相似, 因此可将其提取到基类中, 以让子类共享这些代码。

正如你所看到的那样, 我们有两种类型的步骤:

  • 抽象步骤必须由各个子类来实现
  • 可选步骤已有一些默认实现, 但仍可在需要时进行重写

还有另一种名为钩子的步骤。 钩子是内容为空的可选步骤。 即使不重写钩子, 模板方法也能工作。 钩子通常放置在算法重要步骤的前后, 为子类提供额外的算法扩展点。

真实世界类比

可对典型的建筑方案进行微调以更好地满足客户需求。

模板方法可用于建造大量房屋。 标准房屋建造方案中可提供几个扩展点, 允许潜在房屋业主调整成品房屋的部分细节。

每个建造步骤 (例如打地基、 建造框架、 建造墙壁和安装水电管线等) 都能进行微调, 这使得成品房屋会略有不同。

模板方法模式结构

  1. 抽象类 (Abstract­Class) 会声明作为算法步骤的方法, 以及依次调用它们的实际模板方法。 算法步骤可以被声明为 抽象类型, 也可以提供一些默认实现。
  2. 具体类 (Concrete­Class) 可以重写所有步骤, 但不能重写模板方法自身。

伪代码

本例中的模板方法模式为一款简单策略游戏中人工智能的不同分支提供 “框架”。

一款简单游戏的 AI 类。

游戏中所有的种族都有几乎同类的单位和建筑。 因此你可以在不同的种族上复用相同的 AI 结构, 同时还需要具备重写一些细节的能力。 通过这种方式, 你可以重写半兽人的 AI 使其更富攻击性, 也可以让人类侧重防守, 还可以禁止怪物建造建筑。 在游戏中新增种族需要创建新的 AI 子类, 还需要重写 AI 基类中所声明的默认方法。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 抽象类定义了一个模板方法,其中通常会包含某个由抽象原语操作调用组成的算
// 法框架。具体子类会实现这些操作,但是不会对模板方法做出修改。
class GameAI is
// 模板方法定义了某个算法的框架。
method turn() is
collectResources()
buildStructures()
buildUnits()
attack()

// 某些步骤可在基类中直接实现。
method collectResources() is
foreach (s in this.builtStructures) do
s.collect()

// 某些可定义为抽象类型。
abstract method buildStructures()
abstract method buildUnits()

// 一个类可包含多个模板方法。
method attack() is
enemy = closestEnemy()
if (enemy == null)
sendScouts(map.center)
else
sendWarriors(enemy.position)

abstract method sendScouts(position)
abstract method sendWarriors(position)

// 具体类必须实现基类中的所有抽象操作,但是它们不能重写模板方法自身。
class OrcsAI extends GameAI is
method buildStructures() is
if (there are some resources) then
// 建造农场,接着是谷仓,然后是要塞。

method buildUnits() is
if (there are plenty of resources) then
if (there are no scouts)
// 建造苦工,将其加入侦查编组。
else
// 建造兽族步兵,将其加入战士编组。

// ...

method sendScouts(position) is
if (scouts.length > 0) then
// 将侦查编组送到指定位置。

method sendWarriors(position) is
if (warriors.length > 5) then
// 将战斗编组送到指定位置。

// 子类可以重写部分默认的操作。
class MonstersAI extends GameAI is
method collectResources() is
// 怪物不会采集资源。

method buildStructures() is
// 怪物不会建造建筑。

method buildUnits() is
// 怪物不会建造单位。

模板方法模式适合应用场景

当你只希望客户端扩展某个特定算法步骤, 而不是整个算法或其结构时, 可使用模板方法模式。

模板方法将整个算法转换为一系列独立的步骤, 以便子类能对其进行扩展, 同时还可让超类中所定义的结构保持完整。

当多个类的算法除一些细微不同之外几乎完全一样时, 你可使用该模式。 但其后果就是, 只要算法发生变化, 你就可能需要修改所有的类。

在将算法转换为模板方法时, 你可将相似的实现步骤提取到超类中以去除重复代码。 子类间各不同的代码可继续保留在子类中。

实现方式

  1. 分析目标算法, 确定能否将其分解为多个步骤。 从所有子类的角度出发, 考虑哪些步骤能够通用, 哪些步骤各不相同。
  2. 创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法。 在模板方法中根据算法结构依次调用相应步骤。 可用 final最终修饰模板方法以防止子类对其进行重写。
  3. 虽然可将所有步骤全都设为抽象类型, 但默认实现可能会给部分步骤带来好处, 因为子类无需实现那些方法。
  4. 可考虑在算法的关键步骤之间添加钩子。
  5. 为每个算法变体新建一个具体子类, 它必须实现所有的抽象步骤, 也可以重写部分可选步骤。

模板方法模式优缺点

优点

  • 你可仅允许客户端重写一个大型算法中的特定部分, 使得算法其他部分修改对其所造成的影响减小。
  • 你可将重复代码提取到一个超类中。

缺点

  • 部分客户端可能会受到算法框架的限制。
  • 通过子类抑制默认步骤实现可能会导致违反_里氏替换原则_。
  • 模板方法中的步骤越多, 其维护工作就可能会越困难。

与其他模式的关系

  • 工厂方法模式模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。
  • 模板方法基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略模式基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。

代码示例

模版方法是一种行为设计模式, 它在基类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。

在 C# 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 模版方法模式在 C# 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。

识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。

概念示例

本例说明了模板方法设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

Program.cs: 概念示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
using System;

namespace RefactoringGuru.DesignPatterns.TemplateMethod.Conceptual
{
// The Abstract Class defines a template method that contains a skeleton of
// some algorithm, composed of calls to (usually) abstract primitive
// operations.
//
// Concrete subclasses should implement these operations, but leave the
// template method itself intact.
abstract class AbstractClass
{
// The template method defines the skeleton of an algorithm.
public void TemplateMethod()
{
this.BaseOperation1();
this.RequiredOperations1();
this.BaseOperation2();
this.Hook1();
this.RequiredOperation2();
this.BaseOperation3();
this.Hook2();
}

// These operations already have implementations.
protected void BaseOperation1()
{
Console.WriteLine("AbstractClass says: I am doing the bulk of the work");
}

protected void BaseOperation2()
{
Console.WriteLine("AbstractClass says: But I let subclasses override some operations");
}

protected void BaseOperation3()
{
Console.WriteLine("AbstractClass says: But I am doing the bulk of the work anyway");
}

// These operations have to be implemented in subclasses.
protected abstract void RequiredOperations1();

protected abstract void RequiredOperation2();

// These are "hooks." Subclasses may override them, but it's not
// mandatory since the hooks already have default (but empty)
// implementation. Hooks provide additional extension points in some
// crucial places of the algorithm.
protected virtual void Hook1() { }

protected virtual void Hook2() { }
}

// Concrete classes have to implement all abstract operations of the base
// class. They can also override some operations with a default
// implementation.
class ConcreteClass1 : AbstractClass
{
protected override void RequiredOperations1()
{
Console.WriteLine("ConcreteClass1 says: Implemented Operation1");
}

protected override void RequiredOperation2()
{
Console.WriteLine("ConcreteClass1 says: Implemented Operation2");
}
}

// Usually, concrete classes override only a fraction of base class'
// operations.
class ConcreteClass2 : AbstractClass
{
protected override void RequiredOperations1()
{
Console.WriteLine("ConcreteClass2 says: Implemented Operation1");
}

protected override void RequiredOperation2()
{
Console.WriteLine("ConcreteClass2 says: Implemented Operation2");
}

protected override void Hook1()
{
Console.WriteLine("ConcreteClass2 says: Overridden Hook1");
}
}

class Client
{
// The client code calls the template method to execute the algorithm.
// Client code does not have to know the concrete class of an object it
// works with, as long as it works with objects through the interface of
// their base class.
public static void ClientCode(AbstractClass abstractClass)
{
// ...
abstractClass.TemplateMethod();
// ...
}
}

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Same client code can work with different subclasses:");

Client.ClientCode(new ConcreteClass1());

Console.Write("\n");

Console.WriteLine("Same client code can work with different subclasses:");
Client.ClientCode(new ConcreteClass2());
}
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

在 C++ 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 模版方法模式在 C++ 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。

识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。

概念示例

本例说明了模板方法设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

main.cc: 概念示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* The Abstract Class defines a template method that contains a skeleton of some
* algorithm, composed of calls to (usually) abstract primitive operations.
*
* Concrete subclasses should implement these operations, but leave the template
* method itself intact.
*/
class AbstractClass {
/**
* The template method defines the skeleton of an algorithm.
*/
public:
void TemplateMethod() const {
this->BaseOperation1();
this->RequiredOperations1();
this->BaseOperation2();
this->Hook1();
this->RequiredOperation2();
this->BaseOperation3();
this->Hook2();
}
/**
* These operations already have implementations.
*/
protected:
void BaseOperation1() const {
std::cout << "AbstractClass says: I am doing the bulk of the work\n";
}
void BaseOperation2() const {
std::cout << "AbstractClass says: But I let subclasses override some operations\n";
}
void BaseOperation3() const {
std::cout << "AbstractClass says: But I am doing the bulk of the work anyway\n";
}
/**
* These operations have to be implemented in subclasses.
*/
virtual void RequiredOperations1() const = 0;
virtual void RequiredOperation2() const = 0;
/**
* These are "hooks." Subclasses may override them, but it's not mandatory
* since the hooks already have default (but empty) implementation. Hooks
* provide additional extension points in some crucial places of the
* algorithm.
*/
virtual void Hook1() const {}
virtual void Hook2() const {}
};
/**
* Concrete classes have to implement all abstract operations of the base class.
* They can also override some operations with a default implementation.
*/
class ConcreteClass1 : public AbstractClass {
protected:
void RequiredOperations1() const override {
std::cout << "ConcreteClass1 says: Implemented Operation1\n";
}
void RequiredOperation2() const override {
std::cout << "ConcreteClass1 says: Implemented Operation2\n";
}
};
/**
* Usually, concrete classes override only a fraction of base class' operations.
*/
class ConcreteClass2 : public AbstractClass {
protected:
void RequiredOperations1() const override {
std::cout << "ConcreteClass2 says: Implemented Operation1\n";
}
void RequiredOperation2() const override {
std::cout << "ConcreteClass2 says: Implemented Operation2\n";
}
void Hook1() const override {
std::cout << "ConcreteClass2 says: Overridden Hook1\n";
}
};
/**
* The client code calls the template method to execute the algorithm. Client
* code does not have to know the concrete class of an object it works with, as
* long as it works with objects through the interface of their base class.
*/
void ClientCode(AbstractClass *class_) {
// ...
class_->TemplateMethod();
// ...
}

int main() {
std::cout << "Same client code can work with different subclasses:\n";
ConcreteClass1 *concreteClass1 = new ConcreteClass1;
ClientCode(concreteClass1);
std::cout << "\n";
std::cout << "Same client code can work with different subclasses:\n";
ConcreteClass2 *concreteClass2 = new ConcreteClass2;
ClientCode(concreteClass2);
delete concreteClass1;
delete concreteClass2;
return 0;
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

在 Java 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 模版方法模式在 Java 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。

这里是一些核心 Java 程序库中模版方法的示例:

识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。

重写算法的标准步骤

在本例中, 模版方法模式定义了一个可与社交网络协作的算法。 与特定社交网络相匹配的子类将根据社交网络所提供的 API 来实现这些步骤。

networks

networks/Network.java: 基础社交网络类

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
package refactoring_guru.template_method.example.networks;

/**
* Base class of social network.
*/
public abstract class Network {
String userName;
String password;

Network() {}

/**
* Publish the data to whatever network.
*/
public boolean post(String message) {
// Authenticate before posting. Every network uses a different
// authentication method.
if (logIn(this.userName, this.password)) {
// Send the post data.
boolean result = sendData(message.getBytes());
logOut();
return result;
}
return false;
}

abstract boolean logIn(String userName, String password);
abstract boolean sendData(byte[] data);
abstract void logOut();
}

networks/Facebook.java: 具体社交网络

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
50
51
package refactoring_guru.template_method.example.networks;

/**
* Class of social network
*/
public class Facebook extends Network {
public Facebook(String userName, String password) {
this.userName = userName;
this.password = password;
}

public boolean logIn(String userName, String password) {
System.out.println("\nChecking user's parameters");
System.out.println("Name: " + this.userName);
System.out.print("Password: ");
for (int i = 0; i < this.password.length(); i++) {
System.out.print("*");
}
simulateNetworkLatency();
System.out.println("\n\nLogIn success on Facebook");
return true;
}

public boolean sendData(byte[] data) {
boolean messagePosted = true;
if (messagePosted) {
System.out.println("Message: '" + new String(data) + "' was posted on Facebook");
return true;
} else {
return false;
}
}

public void logOut() {
System.out.println("User: '" + userName + "' was logged out from Facebook");
}

private void simulateNetworkLatency() {
try {
int i = 0;
System.out.println();
while (i < 10) {
System.out.print(".");
Thread.sleep(500);
i++;
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}

networks/Twitter.java: 另一个社交网络

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
50
51
52
package refactoring_guru.template_method.example.networks;

/**
* Class of social network
*/
public class Twitter extends Network {

public Twitter(String userName, String password) {
this.userName = userName;
this.password = password;
}

public boolean logIn(String userName, String password) {
System.out.println("\nChecking user's parameters");
System.out.println("Name: " + this.userName);
System.out.print("Password: ");
for (int i = 0; i < this.password.length(); i++) {
System.out.print("*");
}
simulateNetworkLatency();
System.out.println("\n\nLogIn success on Twitter");
return true;
}

public boolean sendData(byte[] data) {
boolean messagePosted = true;
if (messagePosted) {
System.out.println("Message: '" + new String(data) + "' was posted on Twitter");
return true;
} else {
return false;
}
}

public void logOut() {
System.out.println("User: '" + userName + "' was logged out from Twitter");
}

private void simulateNetworkLatency() {
try {
int i = 0;
System.out.println();
while (i < 10) {
System.out.print(".");
Thread.sleep(500);
i++;
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}

Demo.java: 客户端代码

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
package refactoring_guru.template_method.example;

import refactoring_guru.template_method.example.networks.Facebook;
import refactoring_guru.template_method.example.networks.Network;
import refactoring_guru.template_method.example.networks.Twitter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
* Demo class. Everything comes together here.
*/
public class Demo {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Network network = null;
System.out.print("Input user name: ");
String userName = reader.readLine();
System.out.print("Input password: ");
String password = reader.readLine();

// Enter the message.
System.out.print("Input message: ");
String message = reader.readLine();

System.out.println("\nChoose social network for posting message.\n" +
"1 - Facebook\n" +
"2 - Twitter");
int choice = Integer.parseInt(reader.readLine());

// Create proper network object and send the message.
if (choice == 1) {
network = new Facebook(userName, password);
} else if (choice == 2) {
network = new Twitter(userName, password);
}
network.post(message);
}
}

OutputDemo.png: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Input user name: Jhonatan
Input password: qswe
Input message: Hello, World!

Choose social network for posting message.
1 - Facebook
2 - Twitter
2

Checking user's parameters
Name: Jhonatan
Password: ****
..........

LogIn success on Twitter
Message: 'Hello, World!' was posted on Twitter
User: 'Jhonatan' was logged out from Twitter

在 PHP 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 模版方法模式在 PHP 框架中很常见。 该模式简化了通过类继承对默认框架行为进行扩展时的工作。

识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。

概念示例

本例说明了模版方法设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 PHP 应用案例。

index.php: 概念示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<?php

namespace RefactoringGuru\TemplateMethod\Conceptual;

/**
* The Abstract Class defines a template method that contains a skeleton of some
* algorithm, composed of calls to (usually) abstract primitive operations.
*
* Concrete subclasses should implement these operations, but leave the template
* method itself intact.
*/
abstract class AbstractClass
{
/**
* The template method defines the skeleton of an algorithm.
*/
final public function templateMethod(): void
{
$this->baseOperation1();
$this->requiredOperations1();
$this->baseOperation2();
$this->hook1();
$this->requiredOperation2();
$this->baseOperation3();
$this->hook2();
}

/**
* These operations already have implementations.
*/
protected function baseOperation1(): void
{
echo "AbstractClass says: I am doing the bulk of the work\n";
}

protected function baseOperation2(): void
{
echo "AbstractClass says: But I let subclasses override some operations\n";
}

protected function baseOperation3(): void
{
echo "AbstractClass says: But I am doing the bulk of the work anyway\n";
}

/**
* These operations have to be implemented in subclasses.
*/
abstract protected function requiredOperations1(): void;

abstract protected function requiredOperation2(): void;

/**
* These are "hooks." Subclasses may override them, but it's not mandatory
* since the hooks already have default (but empty) implementation. Hooks
* provide additional extension points in some crucial places of the
* algorithm.
*/
protected function hook1(): void { }

protected function hook2(): void { }
}

/**
* Concrete classes have to implement all abstract operations of the base class.
* They can also override some operations with a default implementation.
*/
class ConcreteClass1 extends AbstractClass
{
protected function requiredOperations1(): void
{
echo "ConcreteClass1 says: Implemented Operation1\n";
}

protected function requiredOperation2(): void
{
echo "ConcreteClass1 says: Implemented Operation2\n";
}
}

/**
* Usually, concrete classes override only a fraction of base class' operations.
*/
class ConcreteClass2 extends AbstractClass
{
protected function requiredOperations1(): void
{
echo "ConcreteClass2 says: Implemented Operation1\n";
}

protected function requiredOperation2(): void
{
echo "ConcreteClass2 says: Implemented Operation2\n";
}

protected function hook1(): void
{
echo "ConcreteClass2 says: Overridden Hook1\n";
}
}

/**
* The client code calls the template method to execute the algorithm. Client
* code does not have to know the concrete class of an object it works with, as
* long as it works with objects through the interface of their base class.
*/
function clientCode(AbstractClass $class)
{
// ...
$class->templateMethod();
// ...
}

echo "Same client code can work with different subclasses:\n";
clientCode(new ConcreteClass1());
echo "\n";

echo "Same client code can work with different subclasses:\n";
clientCode(new ConcreteClass2());

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Same client code can work with different subclasses:
AbstractClass says: I am doing bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses to override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses to override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing bulk of the work anyway

真实世界示例

在本例中, 模版方法模式定义了在社交网络上发布消息的算法框架。 每个子类都代表一个不同的社交网络, 它们虽以不同方式实现所有步骤, 但却会复用基本的算法。

index.php: 真实世界示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php

namespace RefactoringGuru\TemplateMethod\RealWorld;

/**
* The Abstract Class defines the template method and declares all its steps.
*/
abstract class SocialNetwork
{
protected $username;

protected $password;

public function __construct(string $username, string $password)
{
$this->username = $username;
$this->password = $password;
}

/**
* The actual template method calls abstract steps in a specific order. A
* subclass may implement all of the steps, allowing this method to actually
* post something to a social network.
*/
public function post(string $message): bool
{
// Authenticate before posting. Every network uses a different
// authentication method.
if ($this->logIn($this->username, $this->password)) {
// Send the post data. All networks have different APIs.
$result = $this->sendData($message);
// ...
$this->logOut();

return $result;
}

return false;
}

/**
* The steps are declared abstract to force the subclasses to implement them
* all.
*/
abstract public function logIn(string $userName, string $password): bool;

abstract public function sendData(string $message): bool;

abstract public function logOut(): void;
}

/**
* This Concrete Class implements the Facebook API (all right, it pretends to).
*/
class Facebook extends SocialNetwork
{
public function logIn(string $userName, string $password): bool
{
echo "\nChecking user's credentials...\n";
echo "Name: " . $this->username . "\n";
echo "Password: " . str_repeat("*", strlen($this->password)) . "\n";

simulateNetworkLatency();

echo "\n\nFacebook: '" . $this->username . "' has logged in successfully.\n";

return true;
}

public function sendData(string $message): bool
{
echo "Facebook: '" . $this->username . "' has posted '" . $message . "'.\n";

return true;
}

public function logOut(): void
{
echo "Facebook: '" . $this->username . "' has been logged out.\n";
}
}

/**
* This Concrete Class implements the Twitter API.
*/
class Twitter extends SocialNetwork
{
public function logIn(string $userName, string $password): bool
{
echo "\nChecking user's credentials...\n";
echo "Name: " . $this->username . "\n";
echo "Password: " . str_repeat("*", strlen($this->password)) . "\n";

simulateNetworkLatency();

echo "\n\nTwitter: '" . $this->username . "' has logged in successfully.\n";

return true;
}

public function sendData(string $message): bool
{
echo "Twitter: '" . $this->username . "' has posted '" . $message . "'.\n";

return true;
}

public function logOut(): void
{
echo "Twitter: '" . $this->username . "' has been logged out.\n";
}
}

/**
* A little helper function that makes waiting times feel real.
*/
function simulateNetworkLatency()
{
$i = 0;
while ($i < 5) {
echo ".";
sleep(1);
$i++;
}
}

/**
* The client code.
*/
echo "Username: \n";
$username = readline();
echo "Password: \n";
$password = readline();
echo "Message: \n";
$message = readline();

echo "\nChoose the social network to post the message:\n" .
"1 - Facebook\n" .
"2 - Twitter\n";
$choice = readline();

// Now, let's create a proper social network object and send the message.
if ($choice == 1) {
$network = new Facebook($username, $password);
} elseif ($choice == 2) {
$network = new Twitter($username, $password);
} else {
die("Sorry, I'm not sure what you mean by that.\n");
}
$network->post($message);

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Username:
> neo
Password:
> 123123
Message:
> What is the Matrix?

Choose the social network to post the message:
1 - Facebook
2 - Twitter
> 1

Checking user's credentials...
Name: neo
Password: ******
.....

Facebook: 'neo' has logged in successfully.
Facebook: 'neo' has posted 'What is the Matrix?'.
Facebook: 'neo' has been logged out.

在 Python 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 模版方法模式在 Python 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。

识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。

概念示例

本例说明了模板方法设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

main.py: 概念示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
from abc import ABC, abstractmethod


class AbstractClass(ABC):
"""
The Abstract Class defines a template method that contains a skeleton of
some algorithm, composed of calls to (usually) abstract primitive
operations.

Concrete subclasses should implement these operations, but leave the
template method itself intact.
"""

def template_method(self) -> None:
"""
The template method defines the skeleton of an algorithm.
"""

self.base_operation1()
self.required_operations1()
self.base_operation2()
self.hook1()
self.required_operations2()
self.base_operation3()
self.hook2()

# These operations already have implementations.

def base_operation1(self) -> None:
print("AbstractClass says: I am doing the bulk of the work")

def base_operation2(self) -> None:
print("AbstractClass says: But I let subclasses override some operations")

def base_operation3(self) -> None:
print("AbstractClass says: But I am doing the bulk of the work anyway")

# These operations have to be implemented in subclasses.

@abstractmethod
def required_operations1(self) -> None:
pass

@abstractmethod
def required_operations2(self) -> None:
pass

# These are "hooks." Subclasses may override them, but it's not mandatory
# since the hooks already have default (but empty) implementation. Hooks
# provide additional extension points in some crucial places of the
# algorithm.

def hook1(self) -> None:
pass

def hook2(self) -> None:
pass


class ConcreteClass1(AbstractClass):
"""
Concrete classes have to implement all abstract operations of the base
class. They can also override some operations with a default implementation.
"""

def required_operations1(self) -> None:
print("ConcreteClass1 says: Implemented Operation1")

def required_operations2(self) -> None:
print("ConcreteClass1 says: Implemented Operation2")


class ConcreteClass2(AbstractClass):
"""
Usually, concrete classes override only a fraction of base class'
operations.
"""

def required_operations1(self) -> None:
print("ConcreteClass2 says: Implemented Operation1")

def required_operations2(self) -> None:
print("ConcreteClass2 says: Implemented Operation2")

def hook1(self) -> None:
print("ConcreteClass2 says: Overridden Hook1")


def client_code(abstract_class: AbstractClass) -> None:
"""
The client code calls the template method to execute the algorithm. Client
code does not have to know the concrete class of an object it works with, as
long as it works with objects through the interface of their base class.
"""

# ...
abstract_class.template_method()
# ...


if __name__ == "__main__":
print("Same client code can work with different subclasses:")
client_code(ConcreteClass1())
print("")

print("Same client code can work with different subclasses:")
client_code(ConcreteClass2())

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

在 Ruby 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 模版方法模式在 Ruby 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。

识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。

概念示例

本例说明了模板方法设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

main.rb: 概念示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# The Abstract Class defines a template method that contains a skeleton of some
# algorithm, composed of calls to (usually) abstract primitive operations.
#
# Concrete subclasses should implement these operations, but leave the template
# method itself intact.
class AbstractClass
# The template method defines the skeleton of an algorithm.
def template_method
base_operation1
required_operations1
base_operation2
hook1
required_operations2
base_operation3
hook2
end

# These operations already have implementations.

def base_operation1
puts 'AbstractClass says: I am doing the bulk of the work'
end

def base_operation2
puts 'AbstractClass says: But I let subclasses override some operations'
end

def base_operation3
puts 'AbstractClass says: But I am doing the bulk of the work anyway'
end

# These operations have to be implemented in subclasses.
def required_operations1
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end

# @abstract
def required_operations2
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end

# These are "hooks." Subclasses may override them, but it's not mandatory
# since the hooks already have default (but empty) implementation. Hooks
# provide additional extension points in some crucial places of the algorithm.

def hook1; end

def hook2; end
end

# Concrete classes have to implement all abstract operations of the base class.
# They can also override some operations with a default implementation.
class ConcreteClass1 < AbstractClass
def required_operations1
puts 'ConcreteClass1 says: Implemented Operation1'
end

def required_operations2
puts 'ConcreteClass1 says: Implemented Operation2'
end
end

# Usually, concrete classes override only a fraction of base class' operations.
class ConcreteClass2 < AbstractClass
def required_operations1
puts 'ConcreteClass2 says: Implemented Operation1'
end

def required_operations2
puts 'ConcreteClass2 says: Implemented Operation2'
end

def hook1
puts 'ConcreteClass2 says: Overridden Hook1'
end
end

# The client code calls the template method to execute the algorithm. Client
# code does not have to know the concrete class of an object it works with, as
# long as it works with objects through the interface of their base class.
def client_code(abstract_class)
# ...
abstract_class.template_method
# ...
end

puts 'Same client code can work with different subclasses:'
client_code(ConcreteClass1.new)
puts "\n"

puts 'Same client code can work with different subclasses:'
client_code(ConcreteClass2.new)

output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

在 Swift 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 模版方法模式在 Swift 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。

识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。

概念示例

本例说明了模版方法设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。

Example.swift: 概念示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import XCTest


/// The Abstract Protocol and its extension defines a template method that
/// contains a skeleton of some algorithm, composed of calls to (usually)
/// abstract primitive operations.
///
/// Concrete subclasses should implement these operations, but leave the
/// template method itself intact.
protocol AbstractProtocol {

/// The template method defines the skeleton of an algorithm.
func templateMethod()

/// These operations already have implementations.
func baseOperation1()

func baseOperation2()

func baseOperation3()

/// These operations have to be implemented in subclasses.
func requiredOperations1()
func requiredOperation2()

/// These are "hooks." Subclasses may override them, but it's not mandatory
/// since the hooks already have default (but empty) implementation. Hooks
/// provide additional extension points in some crucial places of the
/// algorithm.
func hook1()
func hook2()
}

extension AbstractProtocol {

func templateMethod() {
baseOperation1()
requiredOperations1()
baseOperation2()
hook1()
requiredOperation2()
baseOperation3()
hook2()
}

/// These operations already have implementations.
func baseOperation1() {
print("AbstractProtocol says: I am doing the bulk of the work\n")
}

func baseOperation2() {
print("AbstractProtocol says: But I let subclasses override some operations\n")
}

func baseOperation3() {
print("AbstractProtocol says: But I am doing the bulk of the work anyway\n")
}

func hook1() {}
func hook2() {}
}

/// Concrete classes have to implement all abstract operations of the base
/// class. They can also override some operations with a default implementation.
class ConcreteClass1: AbstractProtocol {

func requiredOperations1() {
print("ConcreteClass1 says: Implemented Operation1\n")
}

func requiredOperation2() {
print("ConcreteClass1 says: Implemented Operation2\n")
}

func hook2() {
print("ConcreteClass1 says: Overridden Hook2\n")
}
}

/// Usually, concrete classes override only a fraction of base class'
/// operations.
class ConcreteClass2: AbstractProtocol {

func requiredOperations1() {
print("ConcreteClass2 says: Implemented Operation1\n")
}

func requiredOperation2() {
print("ConcreteClass2 says: Implemented Operation2\n")
}

func hook1() {
print("ConcreteClass2 says: Overridden Hook1\n")
}
}

/// The client code calls the template method to execute the algorithm. Client
/// code does not have to know the concrete class of an object it works with, as
/// long as it works with objects through the interface of their base class.
class Client {
// ...
static func clientCode(use object: AbstractProtocol) {
// ...
object.templateMethod()
// ...
}
// ...
}


/// Let's see how it all works together.
class TemplateMethodConceptual: XCTestCase {

func test() {

print("Same client code can work with different subclasses:\n")
Client.clientCode(use: ConcreteClass1())

print("\nSame client code can work with different subclasses:\n")
Client.clientCode(use: ConcreteClass2())
}
}

Output.txt: 执行结果

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
Same client code can work with different subclasses:

AbstractProtocol says: I am doing the bulk of the work

ConcreteClass1 says: Implemented Operation1

AbstractProtocol says: But I let subclasses override some operations

ConcreteClass1 says: Implemented Operation2

AbstractProtocol says: But I am doing the bulk of the work anyway

ConcreteClass1 says: Overridden Hook2


Same client code can work with different subclasses:

AbstractProtocol says: I am doing the bulk of the work

ConcreteClass2 says: Implemented Operation1

AbstractProtocol says: But I let subclasses override some operations

ConcreteClass2 says: Overridden Hook1

ConcreteClass2 says: Implemented Operation2

AbstractProtocol says: But I am doing the bulk of the work anyway

真实世界示例

Example.swift: 真实世界示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import XCTest
import AVFoundation
import CoreLocation
import Photos

class TemplateMethodRealWorld: XCTestCase {

/// A good example of Template Method is a life cycle of UIViewController

func testTemplateMethodReal() {

let accessors = [CameraAccessor(), MicrophoneAccessor(), PhotoLibraryAccessor()]

accessors.forEach { item in
item.requestAccessIfNeeded({ status in
let message = status ? "You have access to " : "You do not have access to "
print(message + item.description + "\n")
})
}
}
}

class PermissionAccessor: CustomStringConvertible {

typealias Completion = (Bool) -> ()

func requestAccessIfNeeded(_ completion: @escaping Completion) {

guard !hasAccess() else { completion(true); return }

willReceiveAccess()

requestAccess { status in
status ? self.didReceiveAccess() : self.didRejectAccess()

completion(status)
}
}

func requestAccess(_ completion: @escaping Completion) {
fatalError("Should be overridden")
}

func hasAccess() -> Bool {
fatalError("Should be overridden")
}

var description: String { return "PermissionAccessor" }

/// Hooks
func willReceiveAccess() {}

func didReceiveAccess() {}

func didRejectAccess() {}
}

class CameraAccessor: PermissionAccessor {

override func requestAccess(_ completion: @escaping Completion) {
AVCaptureDevice.requestAccess(for: .video) { status in
return completion(status)
}
}

override func hasAccess() -> Bool {
return AVCaptureDevice.authorizationStatus(for: .video) == .authorized
}

override var description: String { return "Camera" }
}

class MicrophoneAccessor: PermissionAccessor {

override func requestAccess(_ completion: @escaping Completion) {
AVAudioSession.sharedInstance().requestRecordPermission { status in
completion(status)
}
}

override func hasAccess() -> Bool {
return AVAudioSession.sharedInstance().recordPermission == .granted
}

override var description: String { return "Microphone" }
}

class PhotoLibraryAccessor: PermissionAccessor {

override func requestAccess(_ completion: @escaping Completion) {
PHPhotoLibrary.requestAuthorization { status in
completion(status == .authorized)
}
}

override func hasAccess() -> Bool {
return PHPhotoLibrary.authorizationStatus() == .authorized
}

override var description: String { return "PhotoLibrary" }

override func didReceiveAccess() {
/// We want to track how many people give access to the PhotoLibrary.
print("PhotoLibrary Accessor: Receive access. Updating analytics...")
}

override func didRejectAccess() {
/// ... and also we want to track how many people rejected access.
print("PhotoLibrary Accessor: Rejected with access. Updating analytics...")
}
}

Output.txt: 执行结果

1
2
3
4
5
6
You have access to Camera

You have access to Microphone

PhotoLibrary Accessor: Rejected with access. Updating analytics...
You do not have access to PhotoLibrary

在 TypeScript 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 模版方法模式在 TypeScript 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。

识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。

概念示例

本例说明了模版方法设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

index.ts: 概念示例

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* The Abstract Class defines a template method that contains a skeleton of some
* algorithm, composed of calls to (usually) abstract primitive operations.
*
* Concrete subclasses should implement these operations, but leave the template
* method itself intact.
*/
abstract class AbstractClass {
/**
* The template method defines the skeleton of an algorithm.
*/
public templateMethod(): void {
this.baseOperation1();
this.requiredOperations1();
this.baseOperation2();
this.hook1();
this.requiredOperation2();
this.baseOperation3();
this.hook2();
}

/**
* These operations already have implementations.
*/
protected baseOperation1(): void {
console.log('AbstractClass says: I am doing the bulk of the work');
}

protected baseOperation2(): void {
console.log('AbstractClass says: But I let subclasses override some operations');
}

protected baseOperation3(): void {
console.log('AbstractClass says: But I am doing the bulk of the work anyway');
}

/**
* These operations have to be implemented in subclasses.
*/
protected abstract requiredOperations1(): void;

protected abstract requiredOperation2(): void;

/**
* These are "hooks." Subclasses may override them, but it's not mandatory
* since the hooks already have default (but empty) implementation. Hooks
* provide additional extension points in some crucial places of the
* algorithm.
*/
protected hook1(): void { }

protected hook2(): void { }
}

/**
* Concrete classes have to implement all abstract operations of the base class.
* They can also override some operations with a default implementation.
*/
class ConcreteClass1 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass1 says: Implemented Operation1');
}

protected requiredOperation2(): void {
console.log('ConcreteClass1 says: Implemented Operation2');
}
}

/**
* Usually, concrete classes override only a fraction of base class' operations.
*/
class ConcreteClass2 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass2 says: Implemented Operation1');
}

protected requiredOperation2(): void {
console.log('ConcreteClass2 says: Implemented Operation2');
}

protected hook1(): void {
console.log('ConcreteClass2 says: Overridden Hook1');
}
}

/**
* The client code calls the template method to execute the algorithm. Client
* code does not have to know the concrete class of an object it works with, as
* long as it works with objects through the interface of their base class.
*/
function clientCode(abstractClass: AbstractClass) {
// ...
abstractClass.templateMethod();
// ...
}

console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass1());
console.log('');

console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass2());

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

概念示例

让我们来考虑一个一次性密码功能 (OTP) 的例子。 将 OTP 传递给用户的方式多种多样 (短信、 邮件等)。 但无论是短信还是邮件, 整个 OTP 流程都是相同的:

  1. 生成随机的 n 位数字。
  2. 在缓存中保存这组数字以便进行后续验证。
  3. 准备内容。
  4. 发送通知。
  5. 发布。

后续引入的任何新 OTP 类型都很有可能需要进行相同的上述步骤。

因此, 我们会有这样的一个场景, 其中某个特定操作的步骤是相同的, 但实现方式却可能有所不同。 这正是适合考虑使用模板方法模式的情况。

首先, 我们定义一个由固定数量的方法组成的基础模板算法。 这就是我们的模板方法。 然后我们将实现每一个步骤方法, 但不会改变模板方法。

otp.go: 模板方法

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
package main

type iOtp interface {
genRandomOTP(int) string
saveOTPCache(string)
getMessage(string) string
sendNotification(string) error
publishMetric()
}

// type otp struct {
// }

// func (o *otp) genAndSendOTP(iOtp iOtp, otpLength int) error {
// otp := iOtp.genRandomOTP(otpLength)
// iOtp.saveOTPCache(otp)
// message := iOtp.getMessage(otp)
// err := iOtp.sendNotification(message)
// if err != nil {
// return err
// }
// iOtp.publishMetric()
// return nil
// }

type otp struct {
iOtp iOtp
}

func (o *otp) genAndSendOTP(otpLength int) error {
otp := o.iOtp.genRandomOTP(otpLength)
o.iOtp.saveOTPCache(otp)
message := o.iOtp.getMessage(otp)
err := o.iOtp.sendNotification(message)
if err != nil {
return err
}
o.iOtp.publishMetric()
return nil
}

sms.go: 具体实施

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
package main

import "fmt"

type sms struct {
otp
}

func (s *sms) genRandomOTP(len int) string {
randomOTP := "1234"
fmt.Printf("SMS: generating random otp %s\n", randomOTP)
return randomOTP
}

func (s *sms) saveOTPCache(otp string) {
fmt.Printf("SMS: saving otp: %s to cache\n", otp)
}

func (s *sms) getMessage(otp string) string {
return "SMS OTP for login is " + otp
}

func (s *sms) sendNotification(message string) error {
fmt.Printf("SMS: sending sms: %s\n", message)
return nil
}

func (s *sms) publishMetric() {
fmt.Printf("SMS: publishing metrics\n")
}

email.go: 具体实施

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
package main

import "fmt"

type email struct {
otp
}

func (s *email) genRandomOTP(len int) string {
randomOTP := "1234"
fmt.Printf("EMAIL: generating random otp %s\n", randomOTP)
return randomOTP
}

func (s *email) saveOTPCache(otp string) {
fmt.Printf("EMAIL: saving otp: %s to cache\n", otp)
}

func (s *email) getMessage(otp string) string {
return "EMAIL OTP for login is " + otp
}

func (s *email) sendNotification(message string) error {
fmt.Printf("EMAIL: sending email: %s\n", message)
return nil
}

func (s *email) publishMetric() {
fmt.Printf("EMAIL: publishing metrics\n")
}

main.go: 客户端代码

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
package main

import "fmt"

func main() {
// otp := otp{}

// smsOTP := &sms{
// otp: otp,
// }

// smsOTP.genAndSendOTP(smsOTP, 4)

// emailOTP := &email{
// otp: otp,
// }
// emailOTP.genAndSendOTP(emailOTP, 4)
// fmt.Scanln()
smsOTP := &sms{}
o := otp{
iOtp: smsOTP,
}
o.genAndSendOTP(4)

fmt.Println("")
emailOTP := &email{}
o = otp{
iOtp: emailOTP,
}
o.genAndSendOTP(4)

}

output.txt: 执行结果

1
2
3
4
5
6
7
8
9
SMS: generating random otp 1234
SMS: saving otp: 1234 to cache
SMS: sending sms: SMS OTP for login is 1234
SMS: publishing metrics

EMAIL: generating random otp 1234
EMAIL: saving otp: 1234 to cache
EMAIL: sending email: EMAIL OTP for login is 1234
EMAIL: publishing metrics

根据: Golang By Example