大家好,欢迎来到设计模式系列文章(基础篇)的第十一篇内容。在上一篇中,我们完成了结构型模式基础篇的收官,学习了外观模式,其核心是封装复杂子系统、提供统一调用入口,大幅降低客户端与子系统的耦合度。从今天起,我们正式进入设计模式系列(基础篇)的新模块------行为型模式。行为型模式专注于对象之间的交互方式和职责分配,解决"如何让多个对象协同工作、如何合理分配对象职责"的核心问题,今天我们学习行为型模式的第一种常用模式------模板方法模式,它的核心是"定义算法骨架、延迟具体实现",通过抽取公共流程、规范算法步骤,实现代码复用和流程统一,是框架设计、业务流程标准化的核心模式。
在日常开发中,我们经常会遇到"多个业务流程,核心步骤一致,但部分步骤的实现不同"的场景:比如不同支付方式(微信支付、支付宝支付、银行卡支付),核心流程都是"创建支付订单→验证支付信息→执行支付→返回支付结果",但验证支付信息、执行支付的具体逻辑不同;又比如不同类型的报表生成(订单报表、用户报表、商品报表),核心流程都是"获取数据→处理数据→渲染报表→导出报表",但获取数据、处理数据的逻辑因报表类型而异。
如果我们为每个业务场景单独编写代码,会导致大量重复代码(核心流程的步骤重复),同时难以保证所有流程的一致性,后续修改核心流程(如新增"日志记录"步骤)时,需要修改所有相关代码,维护成本极高。而模板方法模式,通过抽取所有流程的公共骨架,将核心步骤定义在父类中,把不同的具体实现延迟到子类中,既实现了公共代码的复用,又保证了流程的统一性,同时支持子类灵活扩展具体步骤,完美解决上述痛点。今天,我们就从核心定义、结构、实战实现、场景对比、避坑指南全维度讲解,帮大家彻底掌握这种"标准化流程、复用公共代码"的实用模式。
一、模板方法模式的核心定义与设计初衷
1. 核心定义
模板方法模式(Template Method Pattern):定义一个算法的骨架,将算法中某些步骤的具体实现延迟到子类中。模板方法模式使得子类可以在不改变算法整体结构的前提下,重新定义算法中的某些步骤,从而实现代码复用和流程统一。
通俗理解:模板方法模式就像我们生活中的"做饭模板"。做不同的菜(如番茄炒蛋、青椒肉丝),核心流程(算法骨架)都是"准备食材→处理食材→烹饪→装盘",这个公共流程就是"模板";而具体步骤的实现不同(准备的食材不同、处理食材的方式不同、烹饪的火候和时间不同),这些不同的实现就由子类(具体菜品)来完成。在代码中,父类定义公共流程(模板方法),子类继承父类,重写不同的具体步骤,既复用了公共流程代码,又实现了子类的个性化需求。
2. 设计初衷(解决的核心问题)
模板方法模式的出现,核心是解决"多个相似流程,公共步骤重复、流程不统一"的痛点,具体解决3个核心问题:
- 实现公共代码复用:抽取多个流程的公共步骤,定义在父类中,子类无需重复编写,减少代码冗余;
- 保证流程统一性:核心流程(算法骨架)由父类统一定义,子类无法修改整体流程,确保所有子类的流程一致,避免出现流程混乱;
- 支持子类灵活扩展:将不同的具体步骤延迟到子类实现,子类可以根据自身需求,灵活重写具体步骤,满足个性化需求,同时不影响整体流程。
3. 设计原则适配
模板方法模式严格贴合行为型模式的设计核心,重点遵循两大核心原则,同时兼顾代码复用:
- 开闭原则:核心流程(模板方法)由父类定义,子类通过重写具体步骤实现扩展,无需修改父类代码,对扩展开放、对修改关闭;
- 里氏替换原则:子类可以替换父类的具体步骤实现,但不会改变父类定义的核心流程,确保子类对象可以替换父类对象,不影响程序运行;
- 单一职责原则:父类负责定义核心流程(模板方法),子类负责实现具体步骤,职责分离,便于维护和扩展。
二、模板方法模式的核心结构(2个核心角色)
模板方法模式的结构非常简洁,核心只有2个角色,无需额外的中间角色,我们以"支付流程"为例,逐一讲解每个角色的职责,清晰理解角色间的交互关系:
1. 抽象类(Abstract Class)
模板方法模式的核心角色,负责定义算法的核心骨架 (模板方法),同时定义流程中的所有步骤------包括公共步骤(父类直接实现,子类无需重写)和抽象步骤(父类定义抽象方法,子类必须重写),还可以定义钩子方法(可选重写,用于控制流程的执行逻辑)。对应支付流程中的"抽象支付类",定义"支付流程"模板方法,包含公共步骤(创建支付订单、返回支付结果)和抽象步骤(验证支付信息、执行支付)。
核心要点:模板方法通常被定义为"不可重写"(如Java中用final修饰),防止子类修改核心流程,确保流程统一性。
2. 具体子类(Concrete Class)
继承抽象类,负责实现抽象类中定义的抽象步骤 ,根据自身业务需求,提供具体的实现逻辑;同时可以选择重写钩子方法,控制流程的执行细节。对应支付流程中的"微信支付子类""支付宝支付子类",分别实现验证支付信息、执行支付的具体逻辑,不改变父类定义的支付流程。
核心关系总结:抽象类定义核心流程(模板方法)和所有步骤,公共步骤由父类实现,抽象步骤由子类实现;子类继承抽象类,重写抽象步骤,实现个性化需求;客户端调用模板方法,触发整个流程,自动执行父类的公共步骤和子类的具体步骤。
三、模板方法模式的核心要素(模板方法+步骤方法)
模板方法模式的核心是"模板方法"和"步骤方法",两者协同工作,构成完整的算法骨架,这也是理解模板方法模式的关键,我们详细拆解:
1. 模板方法(Template Method)
定义在抽象类中,是算法的核心骨架,负责规定所有步骤的执行顺序,通常用final修饰,防止子类修改流程顺序。模板方法中会调用所有步骤方法(公共步骤、抽象步骤、钩子方法),串联起整个业务流程。
示例:支付流程的模板方法,执行顺序为"创建支付订单→验证支付信息→执行支付→返回支付结果",用final修饰,确保所有子类都遵循这个顺序。
2. 步骤方法(Step Method)
模板方法中调用的具体方法,分为3种类型,各司其职,共同完成流程:
- 抽象步骤方法(Abstract Step):抽象类中定义的抽象方法,子类必须重写,对应流程中"不同子类实现不同"的步骤(如验证支付信息、执行支付);
- 具体步骤方法(Concrete Step):抽象类中直接实现的方法,子类无需重写,对应流程中"所有子类都一致"的公共步骤(如创建支付订单、返回支付结果);
- 钩子方法(Hook Method):抽象类中定义的默认实现方法(通常为空实现或返回默认值),子类可以选择重写,用于控制模板方法的执行逻辑(如是否执行某个步骤、调整步骤顺序),是模板方法模式的灵活扩展点。
四、模板方法模式的实战实现(多支付方式场景)
我们以"多支付方式"为场景,使用Java代码实现模板方法模式,完整模拟微信支付、支付宝支付的流程,直观体现模板方法模式"复用公共代码、统一流程、灵活扩展"的核心优势。场景说明:微信支付和支付宝支付的核心流程一致(创建订单→验证信息→执行支付→返回结果),但验证信息、执行支付的具体逻辑不同,同时添加钩子方法,控制是否打印支付日志。
1. 第一步:实现抽象类(抽象支付类,定义模板方法和步骤方法)
定义支付流程的核心骨架(模板方法),区分公共步骤、抽象步骤和钩子方法,确保流程统一性。
bash
// 抽象类:抽象支付类(定义模板方法和步骤方法)
public abstract class AbstractPayment {
// 模板方法:支付核心流程,用final修饰,禁止子类修改流程顺序
public final void pay(String orderId, double amount) {
// 步骤1:创建支付订单(公共步骤,父类实现)
createPaymentOrder(orderId, amount);
// 钩子方法:控制是否打印支付日志(默认打印)
if (needPrintLog()) {
System.out.println("支付日志:开始处理订单【" + orderId + "】的支付,金额:" + amount + "元");
}
// 步骤2:验证支付信息(抽象步骤,子类实现)
boolean verifySuccess = verifyPaymentInfo(orderId, amount);
if (!verifySuccess) {
System.out.println("支付失败:订单【" + orderId + "】支付信息验证失败");
return;
}
// 步骤3:执行支付(抽象步骤,子类实现)
boolean paySuccess = doPay(orderId, amount);
if (!paySuccess) {
System.out.println("支付失败:订单【" + orderId + "】支付执行失败");
return;
}
// 步骤4:返回支付结果(公共步骤,父类实现)
returnPaymentResult(orderId, amount, paySuccess);
}
// 具体步骤方法:创建支付订单(公共步骤,所有子类一致)
private void createPaymentOrder(String orderId, double amount) {
System.out.println("创建支付订单:订单ID【" + orderId + "】,支付金额【" + amount + "元】");
}
// 抽象步骤方法:验证支付信息(子类必须重写,不同支付方式验证逻辑不同)
protected abstract boolean verifyPaymentInfo(String orderId, double amount);
// 抽象步骤方法:执行支付(子类必须重写,不同支付方式执行逻辑不同)
protected abstract boolean doPay(String orderId, double amount);
// 具体步骤方法:返回支付结果(公共步骤,所有子类一致)
private void returnPaymentResult(String orderId, double amount, boolean success) {
System.out.println("订单【" + orderId + "】支付" + (success ? "成功" : "失败") + ",金额:" + amount + "元");
}
// 钩子方法:是否打印支付日志(默认返回true,子类可重写修改)
protected boolean needPrintLog() {
return true;
}
}
2. 第二步:实现具体子类(微信支付、支付宝支付)
继承抽象支付类,重写抽象步骤方法,实现各自的验证逻辑和支付逻辑;可选重写钩子方法,调整流程控制(如支付宝不打印支付日志)。
bash
// 具体子类1:微信支付
public class WechatPayment extends AbstractPayment {
// 重写抽象步骤:验证微信支付信息(如验证openId、签名)
@Override
protected boolean verifyPaymentInfo(String orderId, double amount) {
System.out.println("微信支付:验证订单【" + orderId + "】的微信支付信息(openId验证、签名验证)");
// 模拟验证成功
return true;
}
// 重写抽象步骤:执行微信支付(调用微信支付接口)
@Override
protected boolean doPay(String orderId, double amount) {
System.out.println("微信支付:调用微信支付接口,扣除订单【" + orderId + "】金额【" + amount + "元】");
// 模拟支付成功
return true;
}
}
// 具体子类2:支付宝支付(重写钩子方法,不打印支付日志)
public class AlipayPayment extends AbstractPayment {
// 重写抽象步骤:验证支付宝支付信息(如验证支付宝账号、支付密码)
@Override
protected boolean verifyPaymentInfo(String orderId, double amount) {
System.out.println("支付宝支付:验证订单【" + orderId + "】的支付宝信息(账号验证、密码验证)");
// 模拟验证成功
return true;
}
// 重写抽象步骤:执行支付宝支付(调用支付宝支付接口)
@Override
protected boolean doPay(String orderId, double amount) {
System.out.println("支付宝支付:调用支付宝支付接口,扣除订单【" + orderId + "】金额【" + amount + "元】");
// 模拟支付成功
return true;
}
// 重写钩子方法:不打印支付日志(修改默认逻辑)
@Override
protected boolean needPrintLog() {
return false;
}
}
3. 第三步:客户端调用(统一调用模板方法,无需关心具体实现)
客户端只需创建具体子类对象,调用父类的模板方法,即可触发完整的支付流程,无需关心流程步骤和具体实现,实现统一调用。
bash
// 客户端测试
public class TemplateMethodTest {
public static void main(String[] args) {
// 1. 微信支付
AbstractPayment wechatPayment = new WechatPayment();
System.out.println("========== 微信支付流程 ==========");
wechatPayment.pay("ORDER_001", 199.9);
// 2. 支付宝支付
AbstractPayment alipayPayment = new AlipayPayment();
System.out.println("\n========== 支付宝支付流程 ==========");
alipayPayment.pay("ORDER_002", 299.9);
}
}
运行结果
bash
========== 微信支付流程 ==========
创建支付订单:订单ID【ORDER_001】,支付金额【199.9元】
支付日志:开始处理订单【ORDER_001】的支付,金额:199.9元
微信支付:验证订单【ORDER_001】的微信支付信息(openId验证、签名验证)
微信支付:调用微信支付接口,扣除订单【ORDER_001】金额【199.9元】
订单【ORDER_001】支付成功,金额:199.9元
========== 支付宝支付流程 ==========
创建支付订单:订单ID【ORDER_002】,支付金额【299.9元】
支付宝支付:验证订单【ORDER_002】的支付宝信息(账号验证、密码验证)
支付宝支付:调用支付宝支付接口,扣除订单【ORDER_002】金额【299.9元】
订单【ORDER_002】支付成功,金额:299.9元
核心优势直观体现
- 代码复用性强:创建订单、返回结果的公共代码只写一次,子类无需重复编写,减少冗余;
- 流程统一:核心支付流程由父类统一定义,子类无法修改,确保微信支付、支付宝支付的流程一致;
- 灵活扩展:子类只需重写抽象步骤,即可实现不同支付方式的个性化需求,新增支付方式(如银行卡支付)时,只需新增子类,无需修改父类代码;
- 可扩展性强:通过钩子方法,子类可以灵活控制流程细节(如是否打印日志),无需修改模板方法。
五、模板方法模式的高频应用场景
模板方法模式的核心优势是"统一流程、复用代码、灵活扩展",因此它广泛应用于流程标准化、多场景复用的场景,以下是4个最常用的落地场景,覆盖日常开发、框架底层等领域:
1. 业务流程标准化场景(后端开发最常用)
后端业务中,很多流程具有标准化的步骤,仅部分步骤实现不同,适合用模板方法模式:
- 支付流程:微信、支付宝、银行卡支付,核心流程一致,具体支付逻辑不同;
- 报表生成:订单、用户、商品报表,核心流程(获取数据→处理数据→渲染→导出)一致,具体数据获取、处理逻辑不同;
- 接口请求流程:所有接口的请求流程(参数校验→权限验证→业务处理→返回结果)一致,仅业务处理逻辑不同。
2. 框架底层设计(经典应用)
主流开源框架中,大量使用模板方法模式定义框架的核心流程,让开发者通过子类扩展实现个性化需求:
- Spring 框架:Spring的JdbcTemplate,定义了数据库操作的核心流程(获取连接→创建Statement→执行SQL→处理结果→关闭连接),将SQL执行、结果处理等具体步骤延迟到开发者实现,实现数据库操作的代码复用;
- Spring MVC:Spring MVC的DispatcherServlet,定义了请求处理的核心流程(接收请求→拦截器处理→寻找处理器→执行处理器→返回结果),将处理器执行、结果处理等步骤延迟到开发者实现;
- JUnit 单元测试:JUnit的TestCase类,定义了单元测试的核心流程(初始化→执行测试→清理资源),开发者只需重写测试方法,即可完成单元测试。
3. 多场景复用场景
当多个场景的核心流程一致,仅部分细节不同时,用模板方法模式抽取公共流程,实现多场景复用:
比如不同类型的文件导入(Excel导入、CSV导入、TXT导入),核心流程都是"读取文件→解析数据→校验数据→保存数据",仅读取文件、解析数据的逻辑不同,通过模板方法模式,抽取公共流程,子类实现具体的读取和解析逻辑。
- 代码重构场景
当项目中存在大量重复代码、流程一致但细节不同的方法时,用模板方法模式重构代码,抽取公共流程,将不同细节延迟到子类,减少代码冗余,提升代码可维护性。
六、模板方法模式 vs 其他行为型模式(重点区分)
模板方法模式是行为型模式的基础,与后续要学习的策略模式、工厂方法模式有相似之处,都涉及"抽象定义、子类实现",但核心目标和使用场景差异较大,我们通过表格清晰对比,帮大家快速区分:
| 对比维度 | 模板方法模式 | 策略模式 | 工厂方法模式 |
|---|---|---|---|
| 核心目标 | 定义算法骨架,复用公共流程,统一流程顺序 | 定义多种算法,客户端可动态切换算法,无需关心算法实现 | 定义对象创建的接口,延迟对象创建到子类,实现对象创建的解耦 |
| 核心逻辑 | 父类定义流程骨架,子类实现具体步骤,流程顺序固定 | 封装不同算法,客户端通过选择不同策略,切换算法实现 | 父类定义创建对象的接口,子类实现具体对象的创建 |
| 适用场景 | 流程标准化,多场景核心流程一致、细节不同 | 多种算法可选,客户端需要动态切换算法 | 对象创建复杂,需要解耦对象创建与使用 |
| 核心价值 | 复用公共代码,统一流程,灵活扩展细节 | 算法解耦,动态切换,提升代码灵活性 | 解耦对象创建,统一对象创建接口 |
七、模板方法模式的常见坑与避坑指南
坑1:模板方法过于复杂,包含过多步骤
部分开发者在抽象类中定义的模板方法,包含过多步骤,导致流程过于复杂,子类需要重写大量抽象方法,增加子类的开发成本和维护难度。
避坑指南:模板方法只定义核心流程的关键步骤,拆分冗余步骤,将非核心步骤封装为独立的方法;抽象步骤不宜过多,确保子类开发成本可控。
坑2:子类重写了模板方法,破坏流程统一性
部分开发者忘记给模板方法添加final修饰,导致子类可以重写模板方法,修改核心流程顺序,破坏流程统一性,违背模板方法模式的核心思想。
避坑指南:模板方法必须用final修饰(如Java),禁止子类重写,确保核心流程的统一性,子类只能重写抽象步骤和钩子方法。
坑3:滥用钩子方法,导致流程逻辑混乱
部分开发者过度使用钩子方法,在模板方法中添加大量钩子,控制流程的多个环节,导致流程逻辑混乱,难以理解和维护。
避坑指南:钩子方法仅用于"可选的流程控制",无需每个步骤都添加钩子;钩子方法的默认实现应符合大多数场景的需求,子类仅在特殊场景下重写。
坑4:抽象类与子类耦合过高
部分开发者在抽象类中,直接依赖子类的具体实现,或在子类中直接调用抽象类的私有方法,导致抽象类与子类耦合过高,难以扩展。
避坑指南:抽象类仅定义流程和步骤,不依赖子类的具体实现;子类仅重写抽象步骤和钩子方法,不直接调用抽象类的私有方法,降低耦合度。
八、系列文章预告
本篇文章,我们详细讲解了模板方法模式的核心定义、2大核心角色、核心要素(模板方法+步骤方法)、经典实战代码、高频应用场景和避坑指南,同时对比了模板方法模式与其他相关模式的差异。模板方法模式作为行为型模式的开篇,以"统一流程、复用代码"为核心,是日常开发中流程标准化、代码复用的重要工具,尤其在框架底层设计中应用广泛。
下一篇,我们将继续学习行为型模式的第二种常用模式------策略模式,它的核心是"封装多种算法,动态切换",与模板方法模式不同,策略模式不强调流程的统一性,而是专注于提供多种可选算法,让客户端可以根据需求,动态选择不同的算法实现,无需修改客户端代码,是实现"算法解耦、动态扩展"的核心模式。
策略模式------封装多种算法,实现动态切换与解耦。我们不见不散!