设计模式-行为型模式-策略模式
一、什么是策略模式
策略模式是一种行为设计模式,它允许在运行时选择算法或行为,并将其封装成独立的对象,使得这些算法或行为可以相互替换,而不影响使用它们的客户端。(ChatGPT生成)
主要组成部分:
1、策略(Strategy): 定义了一个算法族、行为或方法,并将其封装在一个接口或抽象类中,使得这些算法可以相互替换。
2、具体策略(Concrete Strategy): 实现了策略接口,提供了具体的算法或行为实现。
3、上下文(Context): 持有一个策略对象的引用,在运行时可动态切换不同的策略。
原理:
1、策略模式允许在运行时动态选择算法或行为,通过将具体的算法或行为封装成策略对象,然后将这些策略对象注入到上下文中。
2、上下文根据不同的需求或条件选择合适的策略对象,并使用它执行特定的算法或行为。
优点:
1、易于扩展和维护: 可以方便地添加新的策略或修改现有策略,不影响原有代码。
2、避免条件语句: 可以避免大量的条件语句,提高代码可读性和可维护性。
3、代码复用: 可以通过共享策略对象来提高代码的复用性。
二、场景模拟
为了应用策略模式,我们模拟一个场景。在一个企业资源管理(ERP)系统中,可能会涉及到成本的计算,其中需要计算的成本例如:钢材、油漆、人工、运输等等,虽然说计算成本都是“单价*数量”,但是有些成本,例如钢材,不仅要计算钢材的成本,而且放置钢材或者其它费用,也需要进行一定的计费;对于人工成本,还需要计算企业担负的五险一金等。
如上图所示,钢材是由吨数*单价计算的,但是要考虑保管费或其他费用,假设一吨n元,后续还可能考虑其它费用等;人工费用,雇员一人可能工资12000元,公司要负担其12%公积金或者医疗保险等;运输要考虑车辆维护费或者日常损耗。(以上仅是模拟,真实环境可能更加复杂或简单)。
三、业务实现
3.1、使用IF-ELSE方法
public static BigDecimal useIfElse(String name, BigDecimal amount, BigDecimal price){if (name.equals("plate")) {// 单价×数量,再加上每吨200元的其它费用return amount.multiply(price).add(amount.multiply(new BigDecimal("200.00")));} else if (name.equals("paints")) {// 单价×数量,再加上每吨200元的其它费用return amount.multiply(price).add(amount.multiply(new BigDecimal("100.00")));} else if (name.equals("employer")) {// 人员×数量,再加上每吨200元的其它费用BigDecimal sum = amount.multiply(price); // 基本工资sum = sum.add(amount.multiply(price.multiply(new BigDecimal("0.12")))); // 加上额外支付的12%公积金return sum;} else if (name.equals("haulage")) {// 吨数×数量,再加上每吨300元的损耗费用return amount.multiply(price).add(amount.multiply(new BigDecimal("300.00")));} else {log.error("无法匹配");return new BigDecimal(0);}}
我们用IF-ELSE方法,确实是可以实现功能,可以看出来很麻烦,看起来很混乱,没有结构,也没办法实现一些定制功能,后续变动起来也比骄困难,因为有一些东西都是写死的。我们来测试一下:
public static void main(String[] args) {// 钢板单价及数量BigDecimal plateAmount = new BigDecimal(15);BigDecimal platePrice = new BigDecimal("888.88");// 油漆价格及数量BigDecimal paintsAmount = new BigDecimal(20);BigDecimal paintsPrice = new BigDecimal("490.00");// 雇员工资及人数BigDecimal employerAmount = new BigDecimal(10);BigDecimal employerPrice = new BigDecimal("10000.00");// 运输价格及吨数BigDecimal haulageAmount = new BigDecimal(25);BigDecimal haulagePrice = new BigDecimal("3000.00");// 计算各个成本BigDecimal plate = useIfElse("plate", plateAmount, platePrice);BigDecimal paints = useIfElse("paints", paintsAmount, paintsPrice);BigDecimal employer = useIfElse("employer", employerAmount, employerPrice);BigDecimal haulage = useIfElse("haulage", haulageAmount, haulagePrice);BigDecimal sum = plate.add(paints).add(employer).add(haulage);log.info("钢材:{}元,油漆{}元,雇员:{}元,运输:{}元,总计:{}元", plate, paints, employer, haulage, sum);}
可以看到确实正确的计算出了结果,但是这种方式不具有拓展性,随着程序功能的增加,会越来越混乱。那有些同学可能会说,为什么不用Switch-Case方法?那就来实践一下。
3.2、使用Switch-Case方法
public static BigDecimal useSwitch(String name, BigDecimal amount, BigDecimal price){switch (name) {case "plate":// 单价×数量,再加上每吨200元的其它费用return amount.multiply(price).add(amount.multiply(new BigDecimal("200.00")));case "paints":// 单价×数量,再加上每吨200元的其它费用return amount.multiply(price).add(amount.multiply(new BigDecimal("100.00")));case "employer":// 人员×数量,再加上每吨200元的其它费用BigDecimal sum = amount.multiply(price); // 基本工资sum = sum.add(amount.multiply(price.multiply(new BigDecimal("0.12")))); // 加上额外支付的12%公积金return sum;case "haulage":// 吨数×数量,再加上每吨300元的损耗费用return amount.multiply(price).add(amount.multiply(new BigDecimal("300.00")));default:log.error("无法匹配");return new BigDecimal(0);}}
使用Switch-Case方法后,确实代码变得比较清晰了,但是一些东西仍然是写死的,不具备很好的扩展性。
3.3、使用策略模式重构代码
其中ICosts是一个接口,里面只有一个方法,就是成本的计算。他的实现类包含钢材、油漆、人工等等,可以有很多。Context是策略控制类,外部可以传递不同策略执行计算成本方法。
3.3.1、编写成本计算接口
public interface ICosts<T, K, V> {/*** 成本计算* @param amount 数量* @param price 单价* @param other 其它信息* @return 成本金额*/BigDecimal calculateCosts(T amount, BigDecimal price, Map<K, V> other);
}
解释一下amount为什么要用T表示,因为这里数量不仅仅是BigDecimal形式的,可能会涉及到人、车辆等等,这种是没有小数点的……;关于other就是一些附加信息,例如钢材的除锈费用、维护费用,人员的五险一金比例等等。
3.3.2、具体成本实现类
钢材
public class GCCosts implements ICosts<BigDecimal, String, BigDecimal> {/** 钢材成本* 1、基本成本:吨数 * 价格;* 2、存放成本:吨数 * 存放消费;* 3、防锈成本:吨数 * 防锈成本;* */@Overridepublic BigDecimal calculateCosts(BigDecimal amount, BigDecimal price, Map<String, BigDecimal> other) {BigDecimal sum = amount.multiply(price);// 计算存放成本if (other.containsKey("CF")) {sum = sum.add(amount.multiply(other.get("CF")));}// 计算防锈成本if (other.containsKey("CX")) {sum = sum.add(amount.multiply(other.get("CX")));}return sum;}
}
油漆
public class YQCosts implements ICosts<BigDecimal, String, BigDecimal> {/** 油漆成本* 1、基础成本:单价 * 数量;* 2、存放成本: 数量 * 存放费用;* */@Overridepublic BigDecimal calculateCosts(BigDecimal amount, BigDecimal price, Map<String, BigDecimal> other) {BigDecimal sum = amount.multiply(price);if (other.containsKey("CF")) {sum = sum.add(amount.multiply(other.get("CF")));}return sum;}
}
人工
public class RGCosts implements ICosts<Long, String, BigDecimal> {/** 人工成本:* 1、基础成本:人数 * 工资;* 2、公积金成本: 人数 * (工资 * 公积金比例);* 3、五险成本:人数 * (工资 * 企业分摊比例);* */@Overridepublic BigDecimal calculateCosts(Long amount, BigDecimal price, Map<String, BigDecimal> other) {// 先计算每个人成本BigDecimal personCosts = price;// 加公积金if (other.containsKey("GJJ")) {personCosts = personCosts.add(price.multiply(other.get("GJJ")));}// 加五险if (other.containsKey("BX")) {personCosts = personCosts.add(price.multiply(other.get("BX")));}return personCosts.multiply(BigDecimal.valueOf(amount)); // 计算总成本}
}
运输
public class YSCosts implements ICosts<BigDecimal, String, BigDecimal> {/** 运输成本:* 1、基础成本:运输数量 * 运输单价;* 2、损耗费用:例如换机油,养护等费用,固定金额(模拟);* */@Overridepublic BigDecimal calculateCosts(BigDecimal amount, BigDecimal price, Map<String, BigDecimal> other) {BigDecimal sum = amount.multiply(price);// 加上固定的养护费用if (other.containsKey("YH")) {sum = sum.add(other.get("YH"));}return sum;}
}
3.3.3、成本策略控制类
public class CostsContext<T, K, V>{private ICosts<T, K, V> iCosts;public CostsContext(ICosts<T, K, V> iCosts) {this.iCosts = iCosts;}public BigDecimal calculateCosts(T amount, BigDecimal price, Map<K, V> other) {return iCosts.calculateCosts(amount, price, other);}
}
此类充当一个调度作用,不同的成本计算就选择不同的实现类,然后执行calculateCosts()方法。
3.3.4、测试
public static void main(String[] args) {// 钢板单价及数量BigDecimal plateAmount = new BigDecimal(15);BigDecimal platePrice = new BigDecimal("888.88");Map<String, BigDecimal> plateMap = new HashMap<String, BigDecimal>(){{put("CF", new BigDecimal("300"));put("CX", new BigDecimal("200"));}};// 油漆价格及数量BigDecimal paintsAmount = new BigDecimal(20);BigDecimal paintsPrice = new BigDecimal("490.00");Map<String, BigDecimal> paintsMap = new HashMap<String, BigDecimal>(){{put("CF", new BigDecimal("300"));}};// 雇员工资及人数// BigDecimal employerAmount = new BigDecimal(10);Long employerAmount = 10L;BigDecimal employerPrice = new BigDecimal("10000.00");Map<String, BigDecimal> employerMap = new HashMap<String, BigDecimal>(){{put("GJJ", new BigDecimal("0.12"));put("BX", new BigDecimal("0.06"));}};// 运输价格及吨数BigDecimal haulageAmount = new BigDecimal(25);BigDecimal haulagePrice = new BigDecimal("3000.00");Map<String, BigDecimal> haulageMap = new HashMap<String, BigDecimal>(){{put("YH", new BigDecimal("1500"));}};// 使用策略模式计算// 钢材CostsContext<BigDecimal, String, BigDecimal> plateCost = new CostsContext<>(new GCCosts());BigDecimal plate = plateCost.calculateCosts(plateAmount, platePrice, plateMap);// 油漆CostsContext<BigDecimal, String, BigDecimal> paintsCost = new CostsContext<>(new YQCosts());BigDecimal paints = paintsCost.calculateCosts(paintsAmount, paintsPrice, paintsMap);// 人工CostsContext<Long, String, BigDecimal> employerCost = new CostsContext<>(new RGCosts());BigDecimal employer = employerCost.calculateCosts(employerAmount, employerPrice, employerMap);// 运输CostsContext<BigDecimal, String, BigDecimal> haulageCost = new CostsContext<>(new YQCosts());BigDecimal haulage = haulageCost.calculateCosts(haulageAmount, haulagePrice, haulageMap);BigDecimal sum = plate.add(paints).add(employer).add(haulage);log.info("钢材:{}元,油漆{}元,雇员:{}元,运输:{}元,总计:{}元", plate, paints, employer, haulage, sum);}
上面是一些各种成本的数量和单价信息,同时使用Map存放了一些特有的成本信息,然后下面我们使用成本策略控制类CostsContext装载不同的实现类来进行成本的计算。
可以看到成功的计算出了结果。我们不仅实现了功能,也为后续的扩展打下了良好的基础,例如后续加新的成本类型进行计算,我们只需要继承ICosts实现不同的方法即可,如果某一个类型的计算加了一些条件,这样我们直接在Map里面添加条件,并在相应的实体类进行处理即可,这样让我们的代码结构变得清晰。
四、小结
if-else是我们初学代码时就接触到的代码,伴随我们走过了很多年,但是我们的思维一定要有提高,不要一有需求,就想到if-else,确实,if-else可以快速地实现功能,我们不可否认,但是随着项目复杂度的提高,if-else已经无法满足我们的需求,它会让我们的代码变得臃肿、复杂、结构混乱,所以我们就想到要用其它方法来替代if-else,让我们整个代码结构变得清晰。
上面我们介绍了使用策略模式来代替一堆的if-else,策略模式的优点远不止如此:
1、具有强的扩展性,例如我们扩充新的成本计算,只需要新建类并实现接口即可;
2、易维护和理解,如何我么要看或者修改钢材的成本计算,我们不用到一堆if-else里面去找具体的代码块,我们只需要进去相应的实现类去看就可以,代码清晰明了,没有其他代码的干扰(体现出隔离性),我们维护起来也得心应手;
3、使用策略模式可以让我们代码变得优美,思维能力和架构水平得到提升。
相关文章:

设计模式-行为型模式-策略模式
一、什么是策略模式 策略模式是一种行为设计模式,它允许在运行时选择算法或行为,并将其封装成独立的对象,使得这些算法或行为可以相互替换,而不影响使用它们的客户端。(ChatGPT生成) 主要组成部分ÿ…...

ResizeObserver观察元素宽度的变化
ResizeObserver观察元素宽度的变化 ResizeObserver观察元素宽度的变化 ResizeObserver观察元素宽度的变化 ResizeObserver 构造函数创建一个新的 ResizeObserver 对象,它可以用于监听 Element 内容盒或边框盒或者 SVGElement 边界尺寸的大小。查看详细说明 案例 &l…...

斐波那契数列,剑指offer,力扣
目录 题目地址: 我们直接看题解吧: 解题方法: 难度分析: 审题目事例提示: 解题思路(动态规划): 代码实现: 补充说明: 代码(优化)&…...
Mac安装CocoaPods
安装HomeBrew 安装 % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"安装失败 % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"curl: (28) F…...
APP专项测试方法和工具的使用(测试新手必看)
APP专项测试 1、网络测试 可使用抓包工具辅助网格测试推荐:fiddler,Charles (1)网络切换2G-3G-4G-wifi-网络信号差--无网(2)网络信号弱关注是否出现ANR、crash 2、中断测试 (1)…...

WordPress网站迁移实战经验
前几日,网站服务器到期,换了服务商,就把我的WordPress的网站迁移到本地电脑了。方便以后文章迁移。 本次迁移网站主要经历以下几个步骤。 1.域名转出。 2.备份数据库及网站文件下载。 3.重新搭建WordPress网站。 4.网站文件及数据库导入。 下面详细介绍下每个步骤的操作…...

3D全景视角,足不出户感知真实场景的魅力
近年来,随着科技的快速发展,普通的平面静态视角已经无法满足我们了,不管是视角框架的限制还是片面的环境展示,都不足以让我们深入了解场景环境。随着VR全景技术的日益成熟,3D全景技术的出现为我们提供了全新的视觉体验…...

C编译环境和预处理(非常详细,建议收藏)
C编译环境和预处理(非常详细,建议收藏) 一、程序的翻译环境和执行环境二、 详解编译链接2.1 翻译环境2.2 编译本身的几个阶段符号汇总、符号表、合并段表、符号表的合并和重定位分别是什么? 2.2 运行环境 三、预处理详解3.1 预定义…...
LeetCode669. Trim a Binary Search Tree
文章目录 一、题目二、题解 一、题目 Given the root of a binary search tree and the lowest and highest boundaries as low and high, trim the tree so that all its elements lies in [low, high]. Trimming the tree should not change the relative structure of the …...

YOLOv8优化策略:轻量级Backbone改进 | VanillaNet极简神经网络模型 | 华为诺亚2023
🚀🚀🚀本文改进:一种极简的神经网络模型 VanillaNet,支持vanillanet_5, vanillanet_6, vanillanet_7, vanillanet_8, vanillanet_9, vanillanet_10, vanillanet_11等版本 🚀🚀🚀YOLOv8改进专栏:http://t.csdnimg.cn/hGhVK 学姐带你学习YOLOv8,从入门到创新,…...

【数据结构(二)】稀疏 sparsearray 数组(1)
文章目录 1. 稀疏数组的应用场景1.1. 一个实际的需求1.2. 基本介绍 2. 稀疏数组转换的思路分析3. 稀疏数组的代码实现3.1. 二维数组转稀疏数组3.2. 稀疏数组转二维数组 4. 课后练习 1. 稀疏数组的应用场景 1.1. 一个实际的需求 问题: 编写的五子棋程序中&…...

MySQL的执行器是怎么工作的
作为优化器后的真正执行语句的层,执行器有三种方式和存储引擎(一般是innoDB)交互 主键索引查询 查询的条件用到了主键,这个是全表唯一的,优化器会选择const类型来查询,然后while循环去根据主键索引的B树结…...

【目标测距】雷达投影测距
文章目录 前言一、读取点云二、点云投影图片三、读取检测信息四、点云投影测距五、学习交流 前言 雷达点云投影相机。图片目标检测,通过检测框约束等等对目标赋予距离。计算消耗较大,适合离线验证操作。在线操作可以只投影雷达检测框。 一、读取点云 py…...
uniapp、小程序canvas相关
1、圆形or圆形头像 //示例 const ctx uni.createCanvasContext(myCanvas); //canvas const round uni.upx2px(72) / 2; // 半径 const x uni.upx2px(92); //目标x轴位置 const y uni.upx2px(236); //目标y轴位置//if 图片是不是静态资源 async > const imgSrc https:/…...

[工业自动化-23]:西门子S7-15xxx编程 - 软件编程 - 西门子PLC人机界面交互HMI功能概述、硬件环境准备、软件环境准备
目录 一、什么是人机界面 二、什么是PLC人机交互界面HMI 三、人机界面设计的功能列表 四、开发主机与PLC的连接方式 五、开发主机与HMI的连接方式 六、HMI组态 一、什么是人机界面 人机界面是指人与机器或系统之间的交互界面。它是人类与计算机或其他设备之间进行信息交换…...

在Ubuntu系统中安装VNC并结合内网穿透实现公网远程访问
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...

java基础练习缺少项目?看这篇文章就够了(上)!
公众号:全干开发 。 专注分享简洁但高质量的动图技术文章! 项目概述 本教程适合刚学习完java基础语法的同学,涉及if语句、循环语句、类的封装、集合等基础概念,使用大量gif图帮助读者演示代码操作、效果等,是一个非常…...
鸿蒙为什么使用typescript 作为开发语言 而不是 flutter 或者 kotlin
猜想如下 dev studio 是基于 idea 二次开发的 ,使用kotlin 应该是更合理 变成 jetbrain 全家桶, 但是 现在android 开发也是kotlin 是不是为了做分割 ,所以不使用kotlin flutter 是谷歌的 安卓也是谷歌的 所以不采用 typescript 是微软的…...

Flutter NestedScrollView 、SliverAppBar全解析,悬浮菜单的应用
在我们开发过程中经常会使用到悬浮菜单的使用,当我们滑动到指定位置后,菜单会自动悬浮。 实现效果如下(左为滑动前、右为滑动后): 上述便是通过NestedScrollView 、SliverAppBar实现的效果,通过两个控件我…...
Mongodb 副本集名称重命名
副本集重命名 要重命名副本集,您必须关闭副本集的所有成员,然后使用新的副本集名称配置每个成员的数据库。 此过程需要停机。 先决条件 确保您的副本集未分片。重命名过程仅适用于未分片的副本集。 在重命名副本集之前,请 对 MongoDB 部…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...