概念

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现


模板方法模式通常涉及两个核心角色:模板类(Abstract Class)和具体实现类(Concrete Class)

  • 模板类(Abstract Class)

    定义了一个算法的骨架,其中包含了算法的各个步骤,其中某些步骤可能由子类实现

  • 具体实现类(Concrete Class)

    实现了模板类中定义的具体步骤。具体实现类负责实现模板类中定义的抽象方法,从而提供算法的具体实现


模板方法模式的核心思想是将算法的不变部分封装到模板类中,而将可变部分留给子类来实现

这种模式的优点在于,可以避免代码的重复,提高代码的复用性,并且可以在不修改模板类的情况下改变算法的某些部分

模板方法模式适用于以下情况:

  • 当希望定义一个算法的骨架,并允许子类为其中的某些步骤提供具体实现时,可以使用模板方法模式
  • 当希望在不修改模板类的情况下改变算法的某些部分时,模板方法模式也是一个很好的选择


举个简单的例子,考虑一个制作咖啡和茶的过程

制作咖啡和茶的过程中都有一些共同的步骤(如煮水、冲泡、加调味品等),而某些步骤可能因为制作的饮料不同而有所不同

模板方法模式可以将制作饮料的算法抽象成一个模板类,并将其中的共同步骤封装到模板类中,而将可变步骤留给具体的实现类来实现

这样,就可以通过继承模板类并重写其中的部分方法来实现不同种类饮料的制作过程,而不需要重复编写相同的代码


实现条件

  1. 存在多个子类

    模板方法模式适用于存在多个子类,并且这些子类之间存在一定的共同行为或者流程的情况

  2. 需要定义一个算法的框架

    模板方法模式适用于需要定义一个算法的框架,并且允许子类根据需要重写其中的某些步骤的情况

  3. 需要避免代码重复

    模板方法模式适用于需要避免代码重复的情况,通过将共同行为或者流程抽象到父类中,可以避免在每个子类中重复编写相同的代码

  4. 需要在运行时动态确定算法的具体实现

    模板方法模式适用于需要在运行时动态确定算法的具体实现的情况,因为模板方法模式允许子类根据需要重写父类中的某些步骤,从而实现不同的算法


优点

  1. 提高代码复用性

    模板方法模式将共同的行为封装到父类中,子类只需要实现特定的步骤即可,从而提高了代码的复用性

  2. 提高扩展性

    模板方法模式通过将算法的框架定义在父类中,并且允许子类重写特定的步骤,提高了系统的扩展性,可以灵活地增加或者修改算法的具体实现

  3. 符合开闭原则

    模板方法模式符合开闭原则,父类中定义的算法框架是稳定的,而具体步骤的实现可以在子类中灵活扩展或者修改,因此对于扩展是开放的,对于修改是关闭的

  4. 提高了系统的适应性

    模板方法模式可以定义一个算法的框架,并且允许子类根据具体情况来实现算法的特定步骤,从而提高了系统的适应性和灵活性


缺点

  1. 可能导致代码的阅读和理解难度增加

    模板方法模式将算法的框架定义在父类中,而具体步骤的实现分散在不同的子类中,可能会导致代码的阅读和理解难度增加

  2. 可能限制子类的灵活性

    模板方法模式将算法的框架固定在父类中,可能会限制子类的灵活性,如果算法的框架需要在运行时动态确定,可能会导致子类的扩展受到限制

  3. 可能增加系统的复杂度

    模板方法模式将算法的具体实现分散在不同的子类中,可能会增加系统的复杂度,特别是当算法的具体实现比较复杂时


实现方式

模板基类

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
/**
* Beverage类,表示一个饮料的制作过程。这个基类定义了基本步骤,
* 包括煮水、冲泡、倒入杯子和添加调料。子类需要覆盖brew和addCondiments方法
* 来实现具体的饮料制作逻辑。
*/
class Beverage {
/**
* prepareBeverage方法,执行饮品的制作流程。
* 此方法调用了一系列抽象方法来完成整个制作过程。
*/
prepareBeverage() {
this.boilWater()
this.brew()
this.pourInCup()
this.addCondiments()
}

/**
* boilWater方法,模拟煮水的过程,并打印相关信息到控制台。
*/
boilWater() {
console.log('煮水')
}

/**
* pourInCup方法,将煮好的水倒入杯子中。
* 这是一个抽象方法,由子类具体实现。
*/
pourInCup() {
console.log('倒入杯子')
}

/**
* brew方法,执行冲泡饮料的操作。
* 这是一个抽象方法,需要子类根据具体饮料类型实现。
*
* @abstract
*/
brew() {
throw new Error('子类必须实现brew方法')
}

/**
* addCondiments方法,向饮品中添加调料或附加物。
* 这是一个抽象方法,需要子类根据具体饮料类型实现。
*
* @abstract
*/
addCondiments() {
throw new Error('子类必须实现addCondiments方法')
}
}

export default Beverage


模板子类

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
/**
* Coffee 类继承自 Beverage 类,实现了冲泡咖啡以及添加糖和牛奶的具体步骤。
*/
import Beverage from '../TemplateMethodPattern'

class Coffee extends Beverage {
/**
* 冲泡咖啡的具体步骤。
*/
brew() {
console.log('冲泡咖啡')
}

/**
* 添加糖和牛奶到咖啡中的具体步骤。
*/
addCondiments() {
console.log('加糖和牛奶')
}
}

export default Coffee



/**
* Tea 类继承自 Beverage 类,实现了冲泡茶的具体步骤。
*/
import Beverage from '../TemplateMethodPattern'

class Tea extends Beverage {
/**
* 冲泡茶叶的方法。
* 此方法为 Tea 类实现的抽象方法,具体冲泡过程为:冲泡茶叶。
*/
brew() {
console.log('冲泡茶叶')
}

/**
* 添加调料的方法。
* 对于茶来说,通常不添加额外的调料,因此此方法实现为不进行任何操作。
*/
addCondiments() {
console.log('不加东西')
}
}

export default Tea


怎么使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 主函数演示如何制作咖啡和茶。
* 本函数不接受参数,也不返回任何值。
*/
import Coffee from '../SubClass/Coffee' // 导入咖啡类
import Tea from '../SubClass/Tea' // 导入茶类

// 制作咖啡的流程
console.log('制作咖啡:')
const coffee = new Coffee() // 创建咖啡实例
coffee.prepareBeverage() // 准备咖啡

// 制作茶的流程
console.log('\n制作茶:')
const tea = new Tea() // 创建茶实例
tea.prepareBeverage() // 准备茶


场景

  1. 框架设计

    在软件框架中,可以使用模板方法模式定义框架的算法骨架,让具体的子类实现算法的细节

    这样做可以确保框架的一致性,并且使得框架更容易扩展和定制

  2. 库函数

    许多编程语言和库中都使用了模板方法模式,例如Java中的Collections.sort()方法就是一个模板方法,它定义了排序算法的骨架,具体的排序算法由Comparator接口的实现类来提供

  3. 算法实现

    在算法设计中,如果一个算法有多个具体实现,但是它们共享相同的算法骨架,那么可以使用模板方法模式

    这样做可以避免代码重复,并且更容易理解和维护

  4. 生命周期管理

    在生命周期管理中,可以使用模板方法模式来定义对象的创建、初始化、销毁等过程

    例如,Android中的Activity生命周期就是一个典型的模板方法模式

  5. 流程控制

    在业务流程中,如果有一系列步骤需要按照特定顺序执行,但是某些步骤的具体实现可能不同,可以使用模板方法模式

    这样做可以保持流程的一致性,并且提高代码的复用性


源代码