设计模式系列(四):模板方法模式

还记得在前面工厂方法里开的水果店么?现在咱们业务扩展了,开始兼售鲜榨果汁。

我们已经有了那么多的水果,接下来做鲜榨果汁就简单多了。就地取材,处理新鲜水果,然后放入榨汁机,榨完倒入杯子,最后打包就行了。

下面我们就实现这个听起来就很靠谱很赚钱的业务。

实现

首先,定义鲜榨果汁的超类,在里面按照我们上面的步骤,写出产出鲜榨果汁的算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class Juice {
protected abstract void prepare();

protected abstract void putMachine();

private void pourCup() {
Log.d("Chen", "倒入定制塑料杯...");
}

private void pack() {
Log.d("Chen", "杯子封口打包...");
}

public void produce() {
Log.d("Chen", "鲜榨果汁现场制作开始!");
prepare();
putMachine();
pourCup();
pack();
Log.d("Chen", "鲜榨果汁现场制作完成!");
}
}

接着,实现具体的果汁。

苹果汁:

1
2
3
4
5
6
7
8
9
10
11
public class AppleJuice extends Juice {
@Override
protected void prepare() {
Log.d("Chen", "取苹果洗净,削皮...");
}

@Override
protected void putMachine() {
Log.d("Chen", "把苹果放入榨汁机...");
}
}

橙汁:

1
2
3
4
5
6
7
8
9
10
11
public class OrangeJuice extends Juice {
@Override
protected void prepare() {
Log.d("Chen", "取橙子洗净,剥皮...");
}

@Override
protected void putMachine() {
Log.d("Chen", "把橙子放入榨汁机...");
}
}

最后我们来调用得到这些果汁。

1
2
3
4
AppleJuice appleJuice = new AppleJuice();
appleJuice.produce();
OrangeJuice orangeJuice = new OrangeJuice();
orangeJuice.produce();

观察我们得到的Log信息:

1
2
3
4
5
6
7
8
9
10
11
12
D/Chen: 鲜榨果汁现场制作开始!
D/Chen: 取苹果洗净,削皮...
D/Chen: 把苹果放入榨汁机...
D/Chen: 倒入定制塑料杯...
D/Chen: 杯子封口打包...
D/Chen: 鲜榨果汁现场制作完成!
D/Chen: 鲜榨果汁现场制作开始!
D/Chen: 取橙子洗净,剥皮...
D/Chen: 把橙子放入榨汁机...
D/Chen: 倒入定制塑料杯...
D/Chen: 杯子封口打包...
D/Chen: 鲜榨果汁现场制作完成!

扩展

模板方法模式还可以设置钩子方法,用来约束模板方法的行为。

比如刚刚的苹果汁和橙汁都是原味的,苹果汁可能没问题,但橙汁有些客人就会觉得有点酸,需要加点糖。

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
public abstract class Juice {
protected abstract void prepare();

protected abstract void putMachine();

private void pourCup() {
Log.d("Chen", "倒入定制塑料杯...");
}

protected void addSugar() {
Log.d("Chen", "加入独家秘制白糖...");
}

private void pack() {
Log.d("Chen", "杯子封口打包...");
}

public void produce() {
Log.d("Chen", "鲜榨果汁现场制作开始!");
prepare();
putMachine();
pourCup();
if(needAddSugar()) {
addSugar();
}
pack();
Log.d("Chen", "鲜榨果汁现场制作完成!");
}

//钩子方法,默认不用加糖
protect boolean needAddSugar() {
return false;
}
}

扩展后的橙汁类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class OrangeJuice extends Juice {
private boolean needAddSugar = false;

@Override
protected void prepare() {
Log.d("Chen", "取橙子洗净,剥皮...");
}

@Override
protected void putMachine() {
Log.d("Chen", "把橙子放入榨汁机...");
}

//是否要加糖,由客人决定
public void setNeedAddSugar(boolean needAddSugar) {
this.needAddSugar = needAddSugar;
}

@Override
protected void needAddSugar() {
return needAddSugar;
}
}

最后调用:

1
2
3
OrangeJuice orangeJuice = new OrangeJuice();
orangeJuice.setNeedAddSugar(true);
orangeJuice.produce();

Log信息:

1
2
3
4
5
6
7
D/Chen: 鲜榨果汁现场制作开始!
D/Chen: 取橙子洗净,剥皮...
D/Chen: 把橙子放入榨汁机...
D/Chen: 倒入定制塑料杯...
D/Chen: 加入独家秘制白糖...
D/Chen: 杯子封口打包...
D/Chen: 鲜榨果汁现场制作完成!

总结

模板方法非常简单,仅仅使用了继承机制,用四个字概况就是:流程封装。但它是一个应用非常广泛的模式。

基本结构

其中,上面最主要的Juice类叫做抽象模板,它里面的方法分为三类

  1. 基本方法: 也叫基本操作,在父类或者子类里实现,并在模板方法里被调用。
  2. 模板方法: 是一个框架,实现对基本方法的调用,完成固定的逻辑。为了防止恶意的操作,可以在该方法上加上final关键字,不允许子类重写。
  3. 钩子方法:用来约束模板方法的行为。

AppleJuice和OrangeJuice类叫做具体模板,实现抽象模板中所定义的基本方法。

应用场景

  • 多个子类有公共的方法,并且逻辑相同时。
  • 重要复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各子类实现。
  • 重构时,把相同代码抽取到父类中,然后通过钩子方法约束其行为。

优点

  • 封装不变部分,扩展可变部分。
  • 提取公共代码,便于维护。
  • 行为由父类控制,子类实现,符合开闭原则。

缺点

降低了代码的可读性。

模板方法模式: 定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

Chen wechat
欢迎扫描二维码,订阅我的博客公众号MiracleChen