当前位置: 首页 > news >正文

23种设计模式:模板方法模式与策略模式

摘要:本文先讲解模板方法模式定义通用流程抽象差异步骤,以炒菜为例),再阐述策略模式封装算法为策略类通过环境角色调用,以促销活动为例),含定义、结构等。

思维导图

1 模板方法模式

1.1 定义

模板方法:؜定义一套通用的执行⁠流程,让子类负责每‏个执行步骤的具体实‌现

模板方法的适用场景:适用于有规范的流程,且执行流程可以复用

作用:大幅节省重复代码量,便于项目扩展、更好维护

1.2 结构

模板方法模式 包含以下主要角色:

抽象类:负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

  • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

  • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

    • 抽象方法:一个抽象方法由抽象类声明、由其具体子类实现。

    • 具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

    • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

1.3 案例实现

【例】炒菜

炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:

1.4 实现思路

目前我们的 “炒菜功能” 代码里,写了两种不同的炒菜方法(炒手撕包菜、炒蒜蓉菜心),但是会发现,这两种方法的流程完全一致、而且大多数代码都是相同的(比如都需要倒油、热油、翻炒,只有放什么菜、放什么调料不一样)。

这种情况下,我们就要运用设计模式 —— 模板方法模式 对代码进行优化。

模板方法模式是行为型设计模式,适用于具有通用处理流程、但处理细节不同的情况。通过定义一个抽象模板类,提供通用业务流程处理逻辑,并将不同的部分定义为抽象方法,由子类具体实现。

在我们的 “炒菜” 场景中,两种炒菜方法的流程都是:

  1. 倒油
  2. 热油
  3. 倒蔬菜
  4. 倒调味料
  5. 翻炒

可以将这些流程抽象为一套模板(抽象类),将每个实现不一样的步骤都定义为抽象方法,比如:

  • 倒蔬菜(一种放包菜、一种放菜心)
  • 倒调味料(一种放辣椒、一种放蒜蓉)

1.5 代码实现

1)定义抽象模板类—— 固定炒菜通用流程

抽象模板类的核心是 “模板方法”(用final修饰,防止子类修改流程顺序),同时区分 “通用步骤” 和 “差异步骤”:

  • 通用步骤:直接在抽象类中实现(所有炒菜方法都一样的操作,比如倒油、热油、翻炒)
  • 差异步骤:定义为抽象方法(由子类实现具体细节,比如放什么菜、放什么调料)

我们创建抽象类AbstractClass,其中cookProcess()是模板方法,固定 5 步炒菜流程;“倒油、热油、翻炒” 是通用步骤直接实现;“倒蔬菜、倒调味料” 是差异步骤定义为抽象方法,代码如下:

package cook.dish;// 抽象模板类:定义炒菜的通用流程
public abstract class AbstractClass {/*** 模板方法:炒菜的完整流程(核心)* 用final修饰 → 子类不能修改流程顺序,保证所有炒菜方法都遵循“倒油→热油→放菜→放调料→翻炒”的顺序*/public final void cookProcess() {this.pourOil();this.heatOil();this.pourVegetable();this.pourSauce();this.fry();}// 第一步:倒油(通用步骤,具体实现固定)public void pourOil() {System.out.println("倒油");}// 第二步:热油(通用步骤,具体实现固定)public void heatOil() {System.out.println("热油");}// 第三步:倒蔬菜(差异步骤,抽象方法,由子类具体实现)public abstract void pourVegetable();// 第四步:倒调味料(差异步骤,抽象方法,由子类具体实现)public abstract void pourSauce();// 第五步:翻炒(通用步骤,具体实现固定)public void fry(){System.out.println("炒啊炒啊炒到熟啊");}
}

2)定义子类的具体实现

我们创建ConcreteClass_BaoCai,继承抽象类AbstractClass,并实现所有抽象方法(也就是 “倒蔬菜” 和 “倒调味料” 的差异步骤),专注于 “炒手撕包菜” 的特有逻辑,代码如下:

package cook.dish;// 具体实现类:对应“炒手撕包菜”(类似文件上传场景的第一种上传方式)
public class ConcreteClass_BaoCai extends AbstractClass {// 实现抽象方法:倒蔬菜(手撕包菜的特有逻辑:放包菜)@Overridepublic void pourVegetable() {System.out.println("下锅的蔬菜是包菜");}// 实现抽象方法:倒调味料(手撕包菜的特有逻辑:放辣椒)@Overridepublic void pourSauce() {System.out.println("下锅的酱料是辣椒");}
}

创建ConcreteClass_CaiXin,继承AbstractClass,实现抽象方法,专注于 “炒蒜蓉菜心” 的特有逻辑

package cook.dish;// 具体实现类:对应“炒蒜蓉菜心”(类似文件上传场景的第二种上传方式)
public class ConcreteClass_CaiXin extends AbstractClass {// 实现抽象方法:倒蔬菜(蒜蓉菜心的特有逻辑:放菜心)@Overridepublic void pourVegetable() {System.out.println("下锅的蔬菜是菜心");}// 实现抽象方法:倒调味料(蒜蓉菜心的特有逻辑:放蒜蓉)@Overridepublic void pourSauce() {System.out.println("下锅的酱料是蒜蓉");}
}

3)创建客户端类—— 调用炒菜功能

package cook.dish;// 客户端类:调用具体的炒菜实现(类似文件上传场景的FileManagerClient)
public class Client {public static void main(String[] args) {// 1. 炒手撕包菜:创建ConcreteClass_BaoCai实例,调用模板方法ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();baoCai.cookProcess(); // 会自动按“倒油→热油→放包菜→放辣椒→翻炒”执行// 2. 炒蒜蓉菜心:创建ConcreteClass_CaiXin实例,调用模板方法ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();caiXin.cookProcess(); // 会自动按“倒油→热油→放菜心→放蒜蓉→翻炒”执行}
}

注意:为防止恶意操作,一般模板方法都加上 final 关键词。

1.6 优缺点

 优点

  • 封装不变部分:算法的不变部分被封装在父类中。
  • 扩展可变部分:子类可以扩展或修改算法的可变部分。
  • 提取公共代码:减少代码重复,便于维护。

 缺点

  • 类数目增加:每个不同的实现都需要一个子类,可能导致系统庞大。

1.7 适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。

  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。


2 策略模式

2.1 定义

策略模式是一种对象行为型设计模式,其核心思想是:针对某一业务场景下的一系列相似算法(或逻辑),先将每个算法(或逻辑)单独封装成独立的 “策略类”,再通过统一的接口让这些策略类可以相互替换。

在实际开发中,当业务场景存在大量 “if...else” 或 “switch” 判断逻辑时,采用策略模式能有效优化代码结构:无需将所有判断逻辑与业务实现混在一起,而是把每种判断对应的逻辑封装成独立策略,既便于后续对单个策略的单独维护与修改,也让整体代码更清晰、可扩展性更强。

2.2 结构

策略模式包含以下几个核心角色:

  • 环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
  • 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
  • 具体策略(Concrete Strategy):实现抽象策略定义的接口或抽象类,包含具体的算法实现。

2.3 案例实现

【例】促销活动

一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:

2.4 实现思路

1. 第一步定义 “策略标准”

先明确所有促销策略的 “统一行为”—— 向客户展示促销内容,因此定义show()方法,让所有具体策略都遵循这个标准,避免出现 “有的策略用show(),有的用display()” 的混乱。​

2. 第二步封装 “具体策略”

将每个节日的促销逻辑单独抽成类,分别实现Strategy接口:​

  • 春节→StrategyA(买一送一)​
  • 中秋→StrategyB(满 200 减 50)​
  • 圣诞→StrategyC(满 1000 加 1 元换购)​

这样做的好处是:修改某节日策略时,不会影响其他策略;新增节日(如国庆)时,只需新建StrategyD实现Strategy接口,无需修改现有代码(符合 “开闭原则”)。​

3. 第三步:设计 “桥梁角色”​

用SalesMan类承担 “使用策略” 的责任:​

  • 通过构造方法接收具体策略(如new SalesMan(new StrategyA())表示 “销售员要推广春节策略”);​
  • 提供salesManShow()方法,内部调用strategy.show()—— 无论传入的是哪个策略,都能通过统一接口触发促销展示,客户端无需关心策略细节。

2.5 代码实现

1. 抽象策略接口(Strategy)—— 定义策略统一标准

public interface Strategy {void show();
}

2. 具体策略类(StrategyA/B/C)—— 实现具体策略逻辑​

这三个类是 “抽象策略角色” 的子类,对应不同的促销规则,属于策略模式的 “具体策略角色”。​

//为春节准备的促销活动A
public class StrategyA implements Strategy {
​public void show() {System.out.println("买一送一");}
}
​
//为中秋准备的促销活动B
public class StrategyB implements Strategy {
​public void show() {System.out.println("满200元减50元");}
}
​
//为圣诞准备的促销活动C
public class StrategyC implements Strategy {
​public void show() {System.out.println("满1000元加一元换购任意200元以下商品");}
}

3. 环境角色类(SalesMan)—— 连接策略与客户端,统一调用

public class SalesMan {                // 持有抽象策略角色的引用(关键:面向接口,不面向具体实现)        private Strategy strategy;          // 构造方法:接收具体策略,初始化时确定要使用的促销策略        public SalesMan(Strategy strategy) { this.strategy = strategy;       }                                  // 对外提供的统一方法:向客户展示促销活动        public void salesManShow(){        strategy.show(); // 调用具体策略的show()方法,无需关心是哪个策略        }                                  
}

                             

2.6 优缺点

   优点

  1. 算法切换自由:可以在运行时根据需要切换算法。
  2. 避免多重条件判断:消除了复杂的条件语句。
  3. 扩展性好:新增算法只需新增一个策略类,无需修改现有代码。

  缺点

  1. 策略类数量增多:每增加一个算法,就需要增加一个策略类。
  2. 所有策略类都需要暴露:策略类需要对外公开,以便可以被选择和使用。

2.7 使用场景

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。

  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。

  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。

  • 系统要求使用算法的客户不应该知道其操作的数据时,可用策略模式来隐藏相关的数据结构。

  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

http://www.dtcms.com/a/349715.html

相关文章:

  • vue 一键打包上传
  • 【车载开发系列】汽车零部件DV与PV试验的差异
  • 【QT/C++】实例理解类间的六大关系之组合关系(Composition)
  • 农业气象监测站:像敏锐的精灵,捕捉农业气象的每一丝变化
  • 18 继续学习
  • 【图像处理基石】基于Real-ESRGAN的实时图像超分辨率技术实现
  • 【GPT-5 与 GPT-4 的主要区别?】
  • 零基础也能写博客:cpolar简化Docsify远程发布流程
  • 基于波前编码成像系统模拟及图像复原的MATLAB实现
  • GPT5的Test-time compute(测试时计算)是什么?
  • 《C++ Primer 第五版》 initializer_list
  • 记一次 element-plus el-table-v2 表格滚动卡顿问题优化
  • Vue SFC Playground 如何正确引入 naive-ui
  • Kubernetes高可用架构设计:多Master节点部署与etcd集群运维深度指南
  • 6.3Element UI 的表单
  • Odoo 非标项目型生产行业解决方案:专业、完整、开源
  • 第十七节:高级材质 - ShaderMaterial揭秘
  • SOME/IP-SD报文中 Entry Format(条目格式)-理解笔记4
  • 从“数据孤岛”到“业财融合”,外贸订单管理ERP重构一体化逻辑
  • 将跨平台框架或游戏引擎开发的 macOS 应用上架 Mac App Store
  • springboot中操作redis的步骤
  • 6.4 Element UI 中的 <el-table> 表格组件
  • 疯狂星期四文案网第49天运营日记
  • 疯狂星期四文案网第50天运营日记
  • 渗透测试报告编写平台 | 简化和自动化渗透测试报告的生成过程。
  • JVM 与容器化部署优化:突破资源隔离的性能瓶颈
  • Ant Design for UI 选择下拉框
  • 详细介绍Vue-Router及其实现原理、路由模式
  • 探索汽车材料新纪元:AUTO TECH 2025广州先进汽车材料展即将震撼来袭
  • Linux系统编程——进程 | 线程