原文地址:https://dev.to/carlillo/design-patterns—template-method-180k
在这篇文章中,我将会介绍什么是模版方法模式,以及如何和什么时候使用它。
模版方法模式:基础概念
模板方法模式是一种行为设计模式,它定义了一个操作中算法的骨架,将一些步骤推迟到子类中。 –Wikipedia
在一个操作中定义一个算法的骨架,推迟一些步骤到子类中。模板方法模式使子类可以在不更改算法结构的情况下重新定义算法的一些步骤。 — Design Patterns: Elements of Reusable Object-Oriented Software
这种模式的主要特点是在不同类型的类中算法稍有不同。当实现具体类时,这些共同的部分在不同的算法中重复。
下面的代码演示了一个经典问题,就是你必须重复一部分稍有不同的算法:
我们可以通过 模板方法模式 使代码更简洁,使得我们避免在算法的不同实现中重复代码。它的 UML 图如下:
注意这个抽象类,包含模板方法和共同的私有方法。模板方法描述了不同步骤的算法。共同的步骤在抽象类中实现,而具体的步骤在每个具体类中是不同的,它们在上述的具体类中实现。
模板方法模式:什么时候使用
- 模板方法模式解决的问题是:使用的算法有些许不同,你需要把你的算法分成不同的步骤,在不同的实现之间有共同点的时候在抽象类中实现。另一方面,不同的步骤将会在具体的类中实现。
- 另一个有趣的场景是当你在不同的类之间拷贝/粘贴代码时,会意识到使用此模式。
- 最后,当你的多数类有相同的行为时,可以使用该模式。
模板方法模式:优势
模板方法模式有许多优势,总结为如下几点:
- 因为你使用抽象类把该类问题的公共代码移除了,所以创建具体的算法实现很简单。
- 因为你避免了重复代码,所以代码更简洁。
- 因为你把算法分割到私有的方法/函数中去了,使得测变得更简单和容易。
模板方法模式:使用 JavaScript 实现一个口袋精灵
现在我将给你演示使用 JavaScript 如何实现这种模式。请记住 JavaScript 缺乏接口和抽象类。因此,理解此模式最好的方法是使用例子。在我们的例子中,我设想了一问题,就是有一个名为 Pokemon
的抽象类来实例化 Pokemon。一个 pokemon 有一组属性 name
、 power
、attack
和 defense
,除此之外还有经典的 toString
方法。pokemon 有类型分类,比如 FightingPokemon
、PoisonPokemon
和 GroundPokemon
它们各自定义了一个具体的方法 calculateDamage
,根据属性和 pokemon 的类型计算数值。下面的 UML 图展示了我刚描述的场景。
使用 JavaScript 代码实现真是场景的代码如下:
![](media/16
599728856873/16601407012291.jpg)
calculateDamage
方法重复出现在每个类中,例如:这是不好的代码(复制/粘贴–重复代码)。此方法可以分割为不同的步骤,就像这样:
calculateDamage(){
const multipliers = this.calculateMultipliers();
const impact = this.calculateImpact(multipliers);
const showDamage(impact);
}
注意我们的方法已经被分割到三个方法中。事实上,其中两个是共用的而另一个是具体的,依据类来实现它(calculateImpact)。我们的 Template-Method
模式已经使用上了,在下面的 UML 中你可以看到通过 Template-Method
更新的版本。
class Pokemon {
constructor(_pokemon) {
this.name = _pokemon.name || 'unknown';
this.power = _pokemon.power || 1;
this.attack = _pokemon.attack || 1;
this.defense = _pokemon.defense || 1;
}
toString() {
return `${this.name} - power: ${this.power}; attack: ${
this.attack
}; defense: ${this.defense}`;
}
calculateMultiplier() {
//Step 1 - Common
return (1 / 2) * this.power * Math.random();
}
showDamage(damage) {
// Step 3 - Common
console.log('Pokemon damage is:', damage);
}
calculateDamage() {
const multipliers = this.calculateMultiplier(); //Step 1;
const damage = this.calculateImpact(multipliers); //Step 2;
this.showDamage(damage); //Step 3;
}
}
你可能已经注意到了 this.calculateImpact
并没有在这个类中实现。这是因为具体的视线将会被放到具体的类中。在我们这个问题中 Pokemon
是一个抽象类。
接下来的步骤由带有 calculateImpact
方法的具体类的实现组成。
class FightingPokemon extends Pokemon {
constructor(_pokemon) {
super(_pokemon);
}
calculateImpact(multipliers) {
return Math.floor((this.attack / this.defense) * multipliers) + 1;
}
}
class PoisonPokemon extends Pokemon {
constructor(_pokemon) {
super(_pokemon);
}
calculateImpact(multipliers) {
return Math.floor((this.attack - this.defense) * multipliers) + 1;
}
}
class GroundPokemon extends Pokemon {
constructor(_pokemon) {
super(_pokemon);
}
calculateImpact(multipliers) {
return Math.floor((this.attack + this.defense) * multipliers) + 1;
}
}
最后,用户端/上下文使用我们的具体 Pokemon 的代码如下:
// Client-Context
const passimian = new FightingPokemon({
name: 'Passimian',
attack: 10,
power: 10,
defense: 10
});
console.log(passimian.toString());
passimian.calculateDamage();
const poipole = new PoisonPokemon({
name: 'Poipole',
attack: 10,
power: 10,
defense: 10
});
console.log(poipole.toString());
poipole.calculateDamage();
const mudsdale = new GroundPokemon({
name: 'Mudsdale',
attack: 10,
power: 10,
defense: 10
});
console.log(mudsdale.toString());
mudsdale.calculateDamage();
上面的代码创建了 Pokemon 的对象且调用了它自身的方法:calculateDamage
。对于用户端/上下文它的实现是透明的,但代码并没有重复。最后,我创建了两个 npm 脚本分别在应用 Template-Method
模式前后运行了一下代码。
npm run step0
npm run step1
总结
模板方法模式是一个可以帮你在项目中减少重复代码的模式,当你个算法有不变与变的部分,后者依赖具体的类。在这篇文章中,你已经看到了使用缺少接口和抽象类的 JavaScript 实现一个简单的模板方法模式。在这例子中你使用的编程语言包含接口和抽象类,可以依照 UML 图来模式。
最重要的不是我给你演示的如何实现此模式,而是认识到这个模式可以解决的问题。以及你什么时候可以或不可以实现所说的模式,这是至关重要的,因为实现此模式很依赖你使用的编程语言。