软件设计模式—模板方法
模板方法(行为模式)
一、概念解释
模板方法模式就像是制定了一份做事的 “通用模板” 或者 “标准流程”。这个模板里规定好了做事的主要步骤,但是其中某些步骤的具体做法可以根据不同的情况灵活变化。
打个比方,你要制作一份美食,美食的制作有固定的几个大步骤,比如准备食材、烹饪、装盘,但不同的美食在准备食材、烹饪方式等具体操作上会有差异,这个固定的大步骤就是模板,而每个步骤的具体做法就是可以灵活调整的部分。
二、生活场景示例
以制作饮料为例,假设你要制作不同类型的热饮,像咖啡和茶,它们的制作过程有一些通用步骤。
1.通用流程:
-
-
- 烧开水。
- 用开水冲泡相应的原料(咖啡粉或茶叶)。
- 把泡好的饮品倒入杯子。
- 可以根据个人口味添加调料(糖、牛奶等)。
-
2.具体差异:
在冲泡原料这一步,制作咖啡用的是咖啡粉,而制作茶用的是茶叶;添加调料时,有些人喝咖啡喜欢加牛奶和糖,而有些人喝茶可能什么都不加。
三、主要元素
元素 | 功能 | 代码实现 |
---|---|---|
通用模板方法或流程载体 | 1. 定义了模板方法 2. 作为子步骤的一些抽象方法或具体方法。 3. 钩子方法(可选),用来决定流程中的某步骤是否执行。 | 抽象父类 注意:在设计时候,确保具体子类不能修改流程部分代码。方法使用关键字final(java)或sealed(C#) |
具体模板或流程载体 | 继承自抽象父类,并实现子步骤的抽象方法 | 具体子类 |
使用者 | 使用抽象父类和具体子类提供的功能,实现其他想要的功能 |
四、代码示例及解释
1.下面用 Java 代码来实现上述制作饮料的场景。
// 抽象类,定义制作饮料的模板方法
abstract class BeverageMaker {
// 模板方法,定义制作饮料的通用流程
public final void makeBeverage() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}
// 烧开水,具体实现固定
private void boilWater() {
System.out.println("Boiling water");
}
// 冲泡原料,具体实现由子类决定
protected abstract void brew();
// 倒入杯子,具体实现固定
private void pourInCup() {
System.out.println("Pouring into cup");
}
// 添加调料,具体实现由子类决定
protected abstract void addCondiments();
// 钩子方法,判断顾客是否需要调料,默认需要
protected boolean customerWantsCondiments() {
return true;
}
}
// 具体子类,咖啡制作类
class CoffeeMaker extends BeverageMaker {
@Override
protected void brew() {
System.out.println("Dripping coffee through filter");
}
@Override
protected void addCondiments() {
System.out.println("Adding sugar and milk");
}
// 重写钩子方法,根据实际情况决定是否添加调料
@Override
protected boolean customerWantsCondiments() {
return false;
}
}
// 具体子类,茶制作类
class TeaMaker extends BeverageMaker {
@Override
protected void brew() {
System.out.println("Steeping the tea");
}
@Override
protected void addCondiments() {
System.out.println("Adding lemon");
}
}
// 使用者,测试类
public class BeverageTest {
public static void main(String[] args) {
System.out.println("Making coffee...");
BeverageMaker coffeeMaker = new CoffeeMaker();
coffeeMaker.makeBeverage();
System.out.println("\nMaking tea...");
BeverageMaker teaMaker = new TeaMaker();
teaMaker.makeBeverage();
}
}
2.C#实现代码:
using System;
// 抽象类,定义制作饮料的模板方法
abstract class BeverageMaker
{
// 模板方法,定义制作饮料的通用流程
public sealed void MakeBeverage()
{
BoilWater();
Brew();
PourInCup();
if (CustomerWantsCondiments())
{
AddCondiments();
}
}
// 烧开水,具体实现固定
private void BoilWater()
{
Console.WriteLine("Boiling water");
}
// 冲泡原料,具体实现由子类决定
protected abstract void Brew();
// 倒入杯子,具体实现固定
private void PourInCup()
{
Console.WriteLine("Pouring into cup");
}
// 添加调料,具体实现由子类决定
protected abstract void AddCondiments();
// 钩子方法,判断顾客是否需要调料,默认需要
protected virtual bool CustomerWantsCondiments()
{
return true;
}
}
// 具体子类,咖啡制作类
class CoffeeMaker : BeverageMaker
{
protected override void Brew()
{
Console.WriteLine("Dripping coffee through filter");
}
protected override void AddCondiments()
{
Console.WriteLine("Adding sugar and milk");
}
// 重写钩子方法,根据实际情况决定是否添加调料
protected override bool CustomerWantsCondiments()
{
return false;
}
}
// 具体子类,茶制作类
class TeaMaker : BeverageMaker
{
protected override void Brew()
{
Console.WriteLine("Steeping the tea");
}
protected override void AddCondiments()
{
Console.WriteLine("Adding lemon");
}
}
//使用者
class Program
{
static void Main()
{
Console.WriteLine("Making coffee...");
BeverageMaker coffeeMaker = new CoffeeMaker();
coffeeMaker.MakeBeverage();
Console.WriteLine("\nMaking tea...");
BeverageMaker teaMaker = new TeaMaker();
teaMaker.MakeBeverage();
}
}
代码解释:
通过模板方法模式,我们可以在保证整体流程不变的情况下,灵活地改变某些步骤的具体实现。
- BeverageMaker 抽象类:
- MakeBeverage 方法是模板方法,它定义了制作饮料的通用步骤,包含了烧开水、冲泡原料、倒入杯子以及根据条件添加调料等步骤。
- BoilWater 和 PourInCup 方法的实现是固定的,因为烧开水和倒入杯子的操作对于制作咖啡和茶来说是一样的。因而使用 private 访问修饰符,表明这是内部实现细节,不希望子类直接访问或重写。
- Brew 和 AddCondiments 方法,在不同饮料制作中存在差异。因而被声明为 protected abstract,这意味着它们是抽象方法,具体的实现由子类完成。
- CustomerWantsCondiments 是一个钩子方法,子类可以根据需要重写它,从而决定是否要执行添加调料这一步骤。使用 protected virtual 修饰,允许子类重写该方法来改变是否添加调料的决策逻辑,默认返回 true。
- CoffeeMaker 类和 TeaMaker 类:
- 这两个类继承自 BeverageMaker 抽象类,并实现了抽象方法 Brew 和 AddCondiments,以体现制作咖啡和茶在冲泡原料和添加调料上的不同。
- CoffeeMaker 类重写了 CustomerWantsCondiments 方法,返回 false,表示制作咖啡时不添加调料。
- Program 类的 Main 方法:
- 创建了 CoffeeMaker 和 TeaMaker 的实例,并调用它们的 MakeBeverage 方法来执行制作饮料的流程。
五、使用场景
当存在一些通用的算法或者流程骨架结构不变,而其步骤中某些具体操作不同的时候,可以使用。常见如下场景:
1. 框架开发
框架开发者明确算法整体结构与通用步骤,但具体实现因场景而异。如 Java 的 Servlet 框架,HttpServlet 类定义了处理 HTTP 请求的通用流程,doGet、doPost 等方法供开发者重写实现业务逻辑。
2. 多子类共性场景
多个子类有共同行为和核心算法,但部分步骤实现不同。例如需要将多种类型的文档(如文本文件、电子表格、演示文稿)转换为 PDF 格式。整个转换过程包含从先读取、再解析、后生成的通用步骤,而每种文件的具体读取、解析和生成的处理细节却不同,可在父类定义通用流程,子类实现每一步的细节。
3. 代码重构优化
代码存在大量重复且代表通用算法流程时,可用此模式重构。像数据处理系统,各模块都有数据读取、处理、存储步骤,但方式不同,可将通用流程放父类模板方法,具体实现由子类完成。
4. 控制子类扩展
通过模板方法和钩子方法控制子类对算法流程的扩展。模板方法规定步骤顺序,钩子方法允许子类特定点扩展或修改。如工作流审批系统,父类定义通用审批流程,子类实现各步骤,钩子方法可控制步骤跳过或额外操作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
2020-02-14 如何用代码来快速批量下载人教社中小学电子教材