模板方法模式

模板方法模式

模板方法设计模式是行为型设计模式中的一种,用在一个功能的完成需要经过一系列步骤,这些步骤是固定的,但是中间某些步骤具体行为是待定的,在不同的场景中行为不同,此时就可以考虑使用模板方法设计模式来完成,不同的场景对因不同的子类实现。

模板方法包含如下的角色:

抽象类/抽象模板(Abstract Class):负责给出一个算法的轮廓,由一个模板方法和若干个基本方法构成

  • 模板方法(该方法也正是模式的核心),定义算法的框架,按照某种顺序调用其中的基本方法。
  • 基本方法,可以包含如下的几种类型
    • 抽象方法:由具体的子类实现,作为定制行为,因为是抽象方法所以子类必须实现。
    • 具体方法:在抽象类中已经提供了具体的实现,一般设置为private。
    • 钩子方法:在抽象类中已经提供了实现(一般是空实现),类似于抽象方法,但是并非强制子类实现,因为已经提供了默认实现。
  • 具体子类/具体实现(Concrete Class):必须实现抽象模板中的抽象方法,以及选择性的重载钩子方法。
image-20231229131822177

抽象模板

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
// 抽象类
public abstract class AbstractClass {
// 模板方法,提供公共的调用顺序,不允许子类override,所以设置为final
public final void templateMethod() {
specificMethod();
abstractMethod1();
abstractMethod2();
hookMethod();
}

// 钩子方法,子类可根据实际情况选择是否要试下该方法
public void hookMethod() {
}

// 具体方法,设置为private,避免子类override,并且不允许外部访问
private void specificMethod() {
System.out.println("抽象类中的具体方法被调用...");
}

// 抽象方法1
public abstract void abstractMethod1();

// 抽象方法2
public abstract void abstractMethod2();

}

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConcreteClass extends AbstractClass {
public void abstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}

public void abstractMethod2() {
System.out.println("抽象方法2的实现被调用...");
}

@Override
public void hookMethod() {
System.out.println("钩子方法被调用...");
}
}

客户端

1
2
3
4
5
6
public class TemplateMethodPattern {
public static void main(String[] args) {
AbstractClass tm = new ConcreteClass();
tm.templateMethod();
}
}

运行

1
2
3
4
5
6
抽象类中的具体方法被调用...
抽象方法1的实现被调用...
抽象方法2的实现被调用...
钩子方法被调用...

Process finished with exit code 0

例子

分析:出国留学手续一般经过以下流程:索取学校资料,提出入学申请,办理因私出国护照、出境卡和公证,申请签证,体检、订机票、准备行装,抵达目标学校等,其中有些业务对各个学校是一样的,但有些业务因学校不同而不同,所以比较适合用模板方法模式来实现。

在本实例中,我们先定义一个出国留学的抽象类 StudyAbroad,里面包含了一个模板方法 TemplateMethod(),该方法中包含了办理出国留学手续流程中的各个基本方法,其中有些方法的处理由于各国都一样,所以在抽象类中就可以实现,但有些方法的处理各国是不同的,必须在其具体子类(如美国留学类 StudyInAmerica)中实现。如果再增加一个国家,只要增加一个子类就可以了

image-20231229132410289

StudyAbroad->抽象类

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
// 抽象类: 出国留学
public abstract class StudyAbroad {
// 模板方法,设置为final不允许子类override
public final void TemplateMethod() {
// 索取学校资料
lookingForSchool();
// 入学申请
applyForEnrol();
// 办理因私出国护照、出境卡和公证
applyForPassport();
// 申请签证
applyForVisa();
// 体检、订机票、准备行装
readyGoAbroad();
// 抵达
arriving();
}

// 具体方法,对外不可见,设置为private
private void applyForPassport() {
System.out.println("三.办理因私出国护照、出境卡和公证:");
}

// 具体方法,对外不可见,设置为private
private void applyForVisa() {
System.out.println("四.申请签证:");
}

// 具体方法,对外不可见,设置为private
private void readyGoAbroad() {
System.out.println("五.体检、订机票、准备行装:");
}

// 索取学校资料
public abstract void lookingForSchool();

// 入学申请
public abstract void applyForEnrol();

// 抵达
public abstract void arriving();
}

StudyInAmerica->美国留学具体实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 具体子类: 美国留学
public class StudyInAmerica extends StudyAbroad {
@Override
public void lookingForSchool() {
System.out.println("一.索取学校以下资料(美国留学):");
}

@Override
public void applyForEnrol() {
System.out.println("二.入学申请(美国留学):");
}

@Override
public void arriving() {
System.out.println("六.抵达目标学校(美国留学):");
}
}

StudyInVatican->梵蒂冈留学具体实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 具体子类: 梵蒂冈留学
public class StudyInVatican extends StudyAbroad {
@Override
public void lookingForSchool() {
System.out.println("一.索取学校以下资料(梵蒂冈):");
}

@Override
public void applyForEnrol() {
System.out.println("二.入学申请(梵蒂冈):");
}

@Override
public void arriving() {
System.out.println("六.抵达目标学校(梵蒂冈):");
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StudyAbroadProcess {
public static void main(String[] args) {
StudyAbroad tm = new StudyInAmerica();
tm.TemplateMethod();

System.out.println();
System.out.println();

StudyAbroad tm1 = new StudyInVatican();
tm1.TemplateMethod();
}
}

测试

image-20231229132632042

优点

  • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展
  • 它在父类中提取了公共的部分代码,便于代码复用
  • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则

缺点

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度
  • 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。

策略模式和模板方法模式

  • 策略模式和模版方法模式的共同点是都封装算法。
  • 策略模式是定义算法家族,让他们之间可以相互替换,模版方法是针对一个算法流程,某个步骤的具体细节交给子类去实现。
  • 策略模式可以改变算法流程,使一个个步骤可以相互替换,模版方法模式算法流程是固定的。
  • 策略模式使用组合来实现,模版方法模式使用继承来实现

应用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  • 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

模板方法模式
http://example.com/2023/10/15/计算机基础/设计模式/18-模板方法模式/
作者
PALE13
发布于
2023年10月15日
许可协议