模板模式(Template Method Pattern) 是一种行为型设计模式,它定义了一个算法的框架,将算法中的一些步骤延迟到子类中实现。模板模式允许子类在不改变算法结构的情况下重定义算法的某些步骤。模板模式的核心思想是通过将通用逻辑封装在基类中,而将具体逻辑交由子类实现,使得代码结构清晰、扩展性高且符合开闭原则。
1. 模板模式的结构
模板模式通常包括以下角色:
- 抽象类(Abstract Class):定义算法的框架,包括模板方法和抽象方法。模板方法定义算法的骨架,抽象方法由子类实现具体逻辑。
- 模板方法(Template Method):在抽象类中定义,通常由多个步骤组成,规定了算法的执行顺序和流程。模板方法会调用抽象方法和其他步骤的实现。
- 具体子类(Concrete Class):实现抽象类中定义的抽象方法,提供算法的具体步骤。
模板模式的 UML 类图
plaintextCopy code+---------------------+
| AbstractClass |<--------------------+
+---------------------+ |
|+ templateMethod() | |
|+ primitiveOperation1() : void (abstract) |
|+ primitiveOperation2() : void (abstract) |
+---------------------+ |
^ |
| |
| |
V |
+---------------------+ +---------------------+
| ConcreteClassA | | ConcreteClassB |
+---------------------+ +---------------------+
|+ primitiveOperation1() |+ primitiveOperation1()
|+ primitiveOperation2() |+ primitiveOperation2()
+---------------------+ +---------------------+
2. 模板模式的实现
假设我们要实现一个制作咖啡和茶的饮品制作流程,咖啡和茶的制作流程大致相同,但每种饮品在某些步骤上会有所不同。我们可以使用模板模式,将共同的制作流程定义在抽象类中,而不同的步骤由具体子类来实现。
1. 定义抽象类(饮料制作模板)
javaCopy codepublic abstract class BeverageTemplate {
// 模板方法定义制作饮料的步骤流程
public final void prepareBeverage() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) { // 使用钩子方法
addCondiments();
}
}
// 通用步骤,所有子类都相同
private void boilWater() {
System.out.println("煮开水");
}
// 抽象方法,由子类实现具体内容
protected abstract void brew();
protected abstract void addCondiments();
// 通用步骤,所有子类都相同
private void pourInCup() {
System.out.println("倒入杯中");
}
// 钩子方法,子类可以选择性地覆盖
protected boolean customerWantsCondiments() {
return true; // 默认添加调料
}
}
2. 具体子类(制作咖啡和制作茶)
制作咖啡
javaCopy codepublic class Coffee extends BeverageTemplate {
@Override
protected void brew() {
System.out.println("用沸水冲泡咖啡");
}
@Override
protected void addCondiments() {
System.out.println("添加糖和牛奶");
}
}
制作茶
javaCopy codepublic class Tea extends BeverageTemplate {
@Override
protected void brew() {
System.out.println("用沸水浸泡茶叶");
}
@Override
protected void addCondiments() {
System.out.println("添加柠檬");
}
// 重写钩子方法,不添加调料
@Override
protected boolean customerWantsCondiments() {
return false;
}
}
3. 客户端代码
在客户端中,可以直接调用 prepareBeverage()
模板方法,无需关心制作流程的具体步骤,因为模板方法已经封装了整个流程:
javaCopy codepublic class Client {
public static void main(String[] args) {
System.out.println("制作咖啡:");
BeverageTemplate coffee = new Coffee();
coffee.prepareBeverage();
System.out.println("\n制作茶:");
BeverageTemplate tea = new Tea();
tea.prepareBeverage();
}
}
输出结果
plaintextCopy code制作咖啡:
煮开水
用沸水冲泡咖啡
倒入杯中
添加糖和牛奶
制作茶:
煮开水
用沸水浸泡茶叶
倒入杯中
3. 模板模式的关键要点
- 模板方法定义了算法骨架:在模板方法中定义了算法的整体执行顺序。
- 抽象方法提供灵活性:算法中的某些步骤被定义为抽象方法,由子类实现,从而赋予子类定制的能力。
- 钩子方法:钩子方法是一种可选的操作,子类可以选择重写或不重写。通过钩子方法,可以在算法流程中插入一些控制逻辑。
4. 模板模式的优缺点
优点
- 代码复用:将通用代码放在抽象类中,子类只需实现差异化的部分,有助于代码的复用。
- 灵活扩展:子类可以定制算法的部分步骤,符合开闭原则。
- 封装不变部分:模板方法封装了算法的固定流程,避免子类对其更改。
缺点
- 继承限制:子类必须继承抽象类才能使用模板方法,这限制了其灵活性。
- 类间耦合增加:抽象类和具体子类之间存在紧密的继承关系,如果算法发生变化,需要修改所有子类。
- 子类实现复杂性:在复杂场景下,子类可能需要实现多个抽象方法,导致实现变得繁琐。
5. 模板模式的应用场景
模板模式适用于以下场景:
- 相似算法结构:当多个类中算法流程一致,但在某些步骤上有所不同,可以使用模板模式,将不同之处交由子类实现。
- 代码复用:将通用逻辑放在抽象类中,避免重复代码。
- 控制流程不变:在算法或流程需要统一管理,但允许部分步骤可变时,适合使用模板模式。
- 多个实现的公共逻辑:比如数据处理流程的不同阶段、页面渲染中的不同模块渲染等。
6. 模板模式的扩展与优化
1. 使用钩子方法增加灵活性
钩子方法是一种可选操作,子类可以选择性地覆盖它。如果在流程中有一些步骤是可选的,钩子方法可以很好地适应这一需求。
在上面的饮品制作示例中,customerWantsCondiments()
就是一个钩子方法,茶类通过覆盖该钩子方法实现了不添加调料的行为。这种方式增加了模板模式的灵活性。
2. 使用策略模式优化模板模式
在模板模式中,算法的结构是固定的,但某些步骤的实现是可变的。如果希望让算法的部分步骤也能动态变化,可以将这些步骤单独提取成策略模式,作为模板模式的成员。
7. 模板模式和其他设计模式的关系
- 与策略模式的关系:模板模式和策略模式都可以用于替换某些行为,但模板模式是通过继承来实现替换,而策略模式是通过组合来实现替换。模板模式关注算法的整体结构,而策略模式关注可替换的具体行为。
- 与工厂方法模式的关系:模板方法模式有时会结合工厂方法模式,在模板方法中使用工厂方法生成对象,从而更灵活地控制子类的行为。
8. 总结
- 模板模式定义了一个算法骨架,并将算法的某些步骤延迟到子类中实现,使得子类可以灵活实现这些步骤。
- 核心要素:模板方法、抽象方法和钩子方法。模板方法规定了执行顺序,抽象方法由子类实现,钩子方法提供了可选的步骤。
- 适用场景:适合流程相似但部分步骤不同的场景,可以复用通用流程,增加代码的扩展性和可维护性。