⑧抽取算法-模板方法模式

抽取公共算法

现在有一个场景,需要让你实现煮方便面和挂面,他们的过程如下。

挂面:

  • 加水
  • 水开后加入挂面
  • 一分钟后加入葱花
  • 装盘

方便面:

  • 加水
  • 水开后加入方便面
  • 一分钟后加入调料包
  • 装盘

可能第一反应会想到继承,父类中抽取加水和装盘的步骤,而中间两步因为实现不同所以不能抽取,而让子类去实现。

虽然中间的两步他们的逻辑相似但是针对的对象不同,一个需要加葱花,一个需要加调料包。而对与程序而言可以理解成算法相同而参数不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class MakeNoodles {
public addWater() {}
// 加入某种类型的面
public addNoodle(noodleType: string) {
console.log("加入" + noodleType);
}
// 等一分钟加入调料
public waitMinuteAddCondiment(condiment: string) {
console.log("一分钟后加入" + condiment);
}
// 装盘
public sabot() {}
}

// 挂面
class MakeFineDriedNoodles extends MakeNoodles {
addNoodle(type: string) {
super.addNoodle(type);
}
waitMinuteAddCondiment(condiment: string) {
super.waitMinuteAddCondiment(condiment);
}
}

当然也可以使用组合的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type MakeNoodlesType = {
new (): any;
};
// 挂面
class MakeFineDriedNoodles {
makeNoodles: MakeNoodles;
constructor(MakeNoodlesConstructor: MakeNoodlesType) {
this.makeNoodles = new MakeNoodlesConstructor();
}
addNoodle(type: string) {
this.makeNoodles.addNoodle(type);
}
waitMinuteAddCondiment(condiment: string) {
this.makeNoodles.waitMinuteAddCondiment(condiment);
}
}

这就是模板方法模式的雏形,最大限度的抽取公共算法,而称为模板方法也是因为此模式经常作为方法调用,而仅限于用于类的继承。

模板方法模式

模板方法模式:在一个方法中定义一个算法骨架,而将一些步骤延续到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤

另外在类的模板方法中经常会定义 Hooks(钩子方法),为子类实现流程控制提供可能。

继承的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
abstract class MakeNoodles {
// 等一分钟加入调料
public waitMinuteAddCondiment(condiment: string) {
if (this.likeCondiment()) {
console.log("一分钟后加入" + condiment);
}
}
public likeCondiment() {
return true;
}
}

// 挂面
class MakeFineDriedNoodles extends MakeNoodles {
likeCondiment() {
return false;
}
waitMinuteAddCondiment(condiment: string) {
super.waitMinuteAddCondiment(condiment);
}
}

组合的方式:

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
class MakeNoodles {
// 等一分钟加入调料
public waitMinuteAddCondiment(condiment: string) {
if (this.likeCondiment()) {
console.log("一分钟后加入" + condiment);
}
}
public likeCondiment() {
return true;
}
}
type MakeNoodlesType = {
new (): any;
};
// 挂面
class MakeFineDriedNoodles {
makeNoodles: MakeNoodles;
constructor(MakeNoodlesConstructor: MakeNoodlesType) {
this.makeNoodles = new MakeNoodlesConstructor();
}
likeCondiment() {
return false;
}
waitMinuteAddCondiment(condiment: string) {
this.makeNoodles.likeCondiment = this.likeCondiment;
this.makeNoodles.waitMinuteAddCondiment(condiment);
}
}

如果对于某些算法是可选的,可以考虑使用 Hooks, 而 Hooks 不一定只是子类控制模板的算法流程,也能使是子类直接使用模板算法里面的 Hooks 控制子类的逻辑。

而这种子类和父类互相调用的场景经常存在,这也涉及到一个原则 避免底层和高层组件之间有明显的环状依赖。

方法中的模板模式

很多场景下模板模式体现的并不明显,如 lodash 中的 add 方法.

1
const add = createMathOperation((augend, addend) => augend + addend, 0);

add 方法中,你可以将传入的回调函数看作是模板方法,但并没有直接返回相加的结果,而是将相加的算法传入 createMathOperation, 而在这个方法中处理数据类型转换的问题。这个方法补充了模板方法的空白。

你也可一把他看作是装饰方法,但不是装饰模式,装饰模式通常会被定义为装饰者和被装饰者实现相同的接口。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信