👉文章示例代码👈

定义

定义一个算法的步骤,并允许子类为一个或者多个步骤提供实现。

模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

场景示例

笔者这里以做番茄炒蛋为例。笔者将做番茄炒蛋的步骤分为以下几步:倒油、放鸡蛋、放番茄、倒调料、翻炒。不考虑多余的细节问题,不同的人做番茄炒蛋的步骤应该是类似的。

创建抽象模板

这里主要定义炒菜的步骤。

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
/**
* @author zhh
* @description 抽象模板类
* @date 2020-02-27 15:47
*/
public abstract class AbstractCook {

/**
* 做饭的整体步骤
*/
protected final void cook() {
this.pourOil();
this.addEgg();
this.addTomato();
this.pourSeasoning();
this.stirFry();
if (needChoppedGreenOnion()) {
this.addChoppedGreenOnion();
}
}

/**
* 倒油
*/
final void pourOil() {
System.out.println("倒入食用油");
}

/**
* 放鸡蛋
*/
final void addEgg() {
System.out.println("放入鸡蛋");
}

/**
* 放番茄
*/
final void addTomato() {
System.out.println("放入番茄");
}

/**
* 翻炒
*/
final void stirFry() {
System.out.println("快速翻炒");
}

/**
* 放葱花
*/
final void addChoppedGreenOnion() {
System.out.println("放点葱花");
}

/**
* 是否需要葱花 (钩子方法)
*/
protected boolean needChoppedGreenOnion() {
return false;
}

/**
* 倒调味品
*/
abstract void pourSeasoning();
}

这里的 needChoppedGreenOnion() 是一个钩子方法,让具体的子类来决定是否添加葱花,默认不添加。

以上的部分方法用 final 修饰,是为了防止子类对其进行重写。

创建具体子类

这里以笔者做饭和女朋友做饭为例。由于两人的口味不同,倒入的调味品会存在些许差异。而且笔者喜欢在番茄炒蛋中加入葱花。

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
/**
* @author zhh
* @description 具体子类
* @date 2020-02-27 16:04
*/
public class MeCook extends AbstractCook {

@Override
void pourSeasoning() {
System.out.println("放点盐");
System.out.println("放点酱油");
}

/**
* 此处覆盖了父类钩子方法的默认实现
*/
@Override
protected boolean needChoppedGreenOnion() {
return true;
}
}


/**
* @author zhh
* @description 具体子类
* @date 2020-02-27 16:07
*/
public class GirlFriendCook extends AbstractCook {

@Override
void pourSeasoning() {
System.out.println("放点盐");
}
}

我们可以看到在 MeCook 类中重写了父类的 needChoppedGreenOnion() 方法,那么对于父类中模板方法执行 needChoppedGreenOnion() 方法时,拿到的其实是子类方法的返回值。

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @author zhh
* @description 测试类
* @date 2020-02-27 16:13
*/
public class Test {

public static void main(String[] args) {
// 我做番茄炒蛋
System.out.println("---我做番茄炒蛋 开始---");
AbstractCook meCook = new MeCook();
meCook.cook();
System.out.println("---我做番茄炒蛋 结束---");

// 女朋友做番茄炒蛋
System.out.println("---女朋友做番茄炒蛋 开始---");
AbstractCook girlFriendCook = new GirlFriendCook();
girlFriendCook.cook();
System.out.println("---女朋友做番茄炒蛋 结束---");
}
}

测试类输出的结果如下:

—我做番茄炒蛋 开始—
倒入食用油
放入鸡蛋
放入番茄
放点盐
放点酱油
快速翻炒
放点葱花
—我做番茄炒蛋 结束—
—女朋友做番茄炒蛋 开始—
倒入食用油
放入鸡蛋
放入番茄
放点盐
快速翻炒
—女朋友做番茄炒蛋 结束—

类结构图

以上示例类的结构图如下所示
image.png

总结

适用场景

  • 算法的整体步骤固定,但其中的个别部分易变。
  • 多个子类存在公共的行为,可以将其提取出来并且集中到一个公共父类当中,避免代码重复。

优点

  • 父类提供公共部分代码,提高复用性。
  • 将不变部分的算法封装到父类中实现,把可变部分的算法由子类继承去实现,提高了扩展性。

缺点

  • 对于每个不同的实现都需要一个子类来实现,导致类数目的增加,增加系统实现的复杂度。
  • 父类中的抽象方法由自子类实现,如果父类添加新的抽象方法,所有的子类都需要修改。

扩展

钩子方法:提供缺省行为,子类可以在必要时进行扩展。

参考