Java设计模式-模板方法模式
模式概述
模板方法模式简介
核心思想:定义一个操作中的算法骨架(模板方法),将算法中某些步骤的具体实现延迟到子类中完成。子类可以在不改变算法整体结构的前提下,重定义这些步骤的行为,从而实现代码复用与扩展的平衡。
模式类型:行为型设计模式(关注对象间的交互与职责分配)。
作用:
- 复用公共逻辑:将多个子类共有的算法步骤提取到父类,避免代码重复。
- 提高扩展性:子类仅需实现差异化的步骤,符合“开闭原则”(对扩展开放,对修改关闭)。
- 规范流程:父类通过模板方法固定算法的整体结构,确保子类行为的一致性。
典型应用场景:
- 多个子类有公共的行为逻辑,但部分步骤实现不同(如数据库访问:连接、执行SQL、关闭连接的流程固定,但不同数据库的驱动实现不同)。
- 框架中需要控制子类的执行流程(如Spring的
JdbcTemplate
封装了JDBC操作的通用流程,具体SQL执行由子类或回调实现)。 - 需要约束子类的行为,确保关键步骤不被遗漏(如订单处理流程:下单→支付→发货→通知,其中支付方式可自定义)。
我认为:模板方法模式是“流程标准化”与“步骤定制化”的完美结合,父类搭骨架,子类填细节。
课程目标
- 理解模板方法模式的核心思想和经典应用场景
- 识别应用场景,使用模板方法模式解决功能要求
- 了解模板方法模式的优缺点
核心组件
角色-职责表
角色 | 职责 | 示例类名 |
---|---|---|
抽象模板角色 | 定义模板方法(算法骨架)和基本方法(具体方法、抽象方法、钩子方法) | AbstractBeverageMaker |
具体模板角色 | 继承抽象模板角色,实现所有抽象方法,并可选重写钩子方法 | CoffeeMaker 、TeaMaker |
类图
下面是一个简化的类图表示,展示了模板方法模式中的主要角色及其交互方式:
传统实现 VS 模板方法模式
案例需求
案例背景:实现饮料制作功能(如咖啡、茶),通用流程为:烧水→冲泡→倒入杯子→添加调料(可选)。不同饮料的冲泡方式(如咖啡粉 vs 茶叶)和调料添加(如加糖 vs 不加)不同。
传统实现(痛点版)
代码实现:
// 传统实现:每个饮料独立编写完整流程
class CoffeeMaker {public void makeCoffee() {boilWater(); // 重复代码brewCoffee(); // 咖啡特有逻辑pourInCup(); // 重复代码addSugar(); // 咖啡特有逻辑}private void boilWater() {System.out.println("烧水:煮沸100℃");}private void brewCoffee() {System.out.println("冲泡:用热水冲咖啡粉");}private void pourInCup() {System.out.println("倒入杯子");}private void addSugar() {System.out.println("添加:糖和牛奶");}
}class TeaMaker {public void makeTea() {boilWater(); // 重复代码brewTea(); // 茶叶特有逻辑pourInCup(); // 重复代码// 茶不需要调料,无需添加}private void boilWater() {System.out.println("烧水:煮沸100℃"); // 重复代码}private void brewTea() {System.out.println("冲泡:用热水泡茶叶");}private void pourInCup() {System.out.println("倒入杯子"); // 重复代码}
}
痛点总结:
- 代码冗余:
boilWater()
、pourInCup()
等方法在每个子类中重复实现。 - 扩展性差:新增饮料(如果汁)需复制大量重复代码,违反开闭原则。
- 流程不可控:无法保证所有饮料遵循相同的基础流程(如漏掉“倒入杯子”步骤)。
模板方法模式 实现(优雅版)
代码实现:
// 抽象模板角色:定义流程骨架
abstract class AbstractBeverageMaker {// 模板方法(final修饰,防止子类修改流程)public final void makeBeverage() {boilWater();brew(); // 调用抽象方法(子类实现)pourInCup();if (needAddCondiments()) { // 调用钩子方法(控制是否添加调料)addCondiments();}}// 具体方法(通用逻辑)private void boilWater() {System.out.println("烧水:煮沸100℃");}// 抽象方法(子类必须实现)protected abstract void brew();// 具体方法(通用逻辑)private void pourInCup() {System.out.println("倒入杯子");}// 钩子方法(默认不添加调料,子类可选重写)protected boolean needAddCondiments() {return false;}// 钩子方法关联的具体操作(子类可选重写)protected void addCondiments() {// 默认空实现}
}// 具体模板角色:咖啡制作
class CoffeeMaker extends AbstractBeverageMaker {@Overrideprotected void brew() {System.out.println("冲泡:用热水冲咖啡粉");}@Overrideprotected boolean needAddCondiments() {return true; // 咖啡需要添加调料}@Overrideprotected void addCondiments() {System.out.println("添加:糖和牛奶");}
}// 具体模板角色:茶叶制作
class TeaMaker extends AbstractBeverageMaker {@Overrideprotected void brew() {System.out.println("冲泡:用热水泡茶叶");}// 不重写needAddCondiments(),默认不添加调料
}
优势:
- 消除冗余:公共方法(如
boilWater()
)在抽象类中实现,子类无需重复。 - 流程可控:模板方法通过
final
修饰,确保子类无法修改基础流程。 - 灵活扩展:子类仅需实现抽象方法(如
brew()
),并通过钩子方法(needAddCondiments()
)控制可选逻辑。
局限:
- 类数量增加:每个差异化的子类需单独定义,可能增加系统复杂度。
- 抽象类设计成本:需合理规划抽象方法与钩子方法,过度设计可能导致冗余。
模式变体
- 具体模板方法:将模板方法声明为
final
,禁止子类修改算法骨架(强制遵循固定流程)。 - 钩子方法(Hook Method):提供默认实现的方法(通常返回布尔值或空操作),子类可选择是否重写以影响模板方法的行为(如上述案例中的
needAddCondiments()
)。 - 参数化模板:在模板方法中添加参数,允许子类通过参数调整行为(如数据库操作模板支持传入事务隔离级别)。
最佳实践
建议 | 理由 |
---|---|
抽象类保持稳定 | 模板方法模式的核心是流程固定,频繁修改抽象类会导致所有子类连锁改动。 |
钩子方法提供默认实现 | 减少子类必须重写的负担,仅当需要差异化时才覆盖。 |
避免过度抽象 | 若子类间差异极小(如仅有1个步骤不同),可能更适合直接继承而非模板模式。 |
模板方法用final 修饰 | 防止子类意外修改算法骨架,确保流程一致性。 |
一句话总结
模板方法模式通过“父类定义流程骨架,子类实现差异化步骤”,在保证流程规范的同时,实现了代码复用与灵活扩展。
如果关注Java设计模式内容,可以查阅作者的其他Java设计模式系列文章。😊