定义
定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤 。
和工厂方法的定义有一点像。但是工厂方法着重于对象的创建过程,它提供了一种灵活的实例化策略;而模板方法则专注于定义算法的框架,将固定不变的“模板”放在父类,变化的部分放在子类中,由子类去实现。
所以当需要控制对象的创建过程,尤其是当类型的选择依赖于运行时信息时,应考虑使用工厂方法。而当多个类有相似的操作流程,但部分步骤需要定制时,模板方法是更合适的选择。
构成
抽象类(Abstract Class):定义了算法的骨架,包括一个模板方法以及其他基本方法。
1. 模板方法(Template Method):定义算法的骨架,按某种顺序调用模板方法。模板方法通常是final
的,以防止子类改变算法的结构。
2. 基本方法(Primitive Operations):抽象方法,定义算法中的各个步骤,由子类实现具体逻辑。
具体子类(Concrete Class):
1. 实现抽象类中定义的基本方法,从而实现算法中的各个步骤。
2. 具体子类不改变模板方法中定义的算法结构,但可以重写基本方法以提供不同的实现。
UML图
模板方法模式的代码实现
场景描述
这个场景中,我们设计了一个饮品制作系统,现在描述一下咖啡和茶的制作流程。
整个流程分为四个步骤:烧水、冲泡、将制作好的饮品倒入杯中、以及添加调料。通过应用模板方法模式,我们可以构建了一个抽象类来封装流程中的通用模板步骤【烧水以及将制作好的饮品倒入杯中】,确保每种饮品制作都会执行这些基本操作。
同时,在具体子类(咖啡、茶类)中分别实现冲泡和添加调料这两个变化的行为,这样,就可以灵活地支持了各种饮品特有步骤的定制化实现。
代码实现
抽象类定义了算法的骨架,包括模板方法以及其他基本方法。
模板方法:boilWater()
、pourInCup()
其他基本方法:brew()
、addCondiments()
// 抽象类,定义制作饮品的模板方法
abstract class Beverage {
// 模板方法,定义制作饮品的步骤
public final void prepareBeverage() {
boilWater();
brew();
pourInCup();
addCondiments();
}
// 烧水
private void boilWater() {
System.out.println("烧水");
}
// 冲泡(抽象方法,由子类实现)
protected abstract void brew();
// 倒入杯中
private void pourInCup() {
System.out.println("将饮品倒入杯中");
}
// 添加调料(抽象方法,由子类实现)
protected abstract void addCondiments();
}
具体子类 // 咖啡类,继承抽象类并实现抽象方法
class Coffee extends Beverage {
@Override
protected void brew() {
System.out.println("冲泡咖啡");
}
@Override
protected void addCondiments() {
System.out.println("添加糖和牛奶");
}
}
// 茶类,继承抽象类并实现抽象方法
class Tea extends Beverage {
@Override
protected void brew() {
System.out.println("浸泡茶叶");
}
@Override
protected void addCondiments() {
System.out.println("添加柠檬");
}
}
客户端代码 public class TemplateMethodPatternDemo {
public static void main(String[] args) {
// 制作咖啡
Beverage coffee = new Coffee();
System.out.println("制作咖啡:");
coffee.prepareBeverage();
System.out.println();
// 制作茶
Beverage tea = new Tea();
System.out.println("制作茶:");
tea.prepareBeverage();
}
}
总结
优点
1. 代码复用性高:模板方法模式通过将公共的、不变的代码放在父类的模板方法中,将变化的部分委托给子类实现,实现了方法重用
2. 扩展性强:遵循开闭原则,当需要增加新的功能或者修改已有功能时,只需增加新的子类或者修改子类的实现,不需要改动父类的模板方法,降低模块耦合度。
缺点
继承的局限性:过度依赖继承可能会带来问题,如“脆弱基类问题”,即对父类的修改可能会影响到所有子类。
应用场景
1. 多个子类有公有方法,而且逻辑基本相同,抽象出公共方法
2. 重要、复杂的算法,可以把核心算法设计为模版方法,具体的逻辑实现则由各个子类实现重构时完成