策略模式:灵活的算法封装与切换

策略模式是一种行为型设计模式,它将一组算法封装成独立的类,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。本文将以一个收银系统为例,详细介绍策略模式的实现和应用。

什么是策略模式?

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户端。

核心组件:

  • 策略接口/抽象类:定义了算法的公共接口
  • 具体策略类:实现策略接口的具体算法
  • 上下文类:维护一个对策略对象的引用,负责将客户端请求委派给策略对象

收银系统中的策略模式实现

1. 策略抽象类

首先,我们定义一个策略抽象类CashStrategy,它规定了所有收费策略必须实现的方法:

public abstract class CashStrategy
{public abstract decimal AcceptCash(decimal originalAmount);
}

2. 具体策略实现

接下来,实现几种具体的收费策略:

// 正常收费策略
public class NormalCash : CashStrategy
{public override decimal AcceptCash(decimal originalAmount) => originalAmount;
}// 打折策略
public class DiscountCash : CashStrategy
{private readonly decimal _discountRate;public DiscountCash(decimal rate) => _discountRate = rate;public override decimal AcceptCash(decimal originalAmount)=> originalAmount * _discountRate;
}// 满减策略
public class ReturnCash : CashStrategy
{private readonly decimal _condition;private readonly decimal _returnAmount;public ReturnCash(decimal condition, decimal returnAmount)=> (_condition, _returnAmount) = (condition, returnAmount);public override decimal AcceptCash(decimal originalAmount)=> originalAmount - Math.Floor(originalAmount / _condition) * _returnAmount;
}// 增收策略
public class RevenueGrowth : CashStrategy
{private readonly decimal _surchargeAmount;public RevenueGrowth(decimal amount) => _surchargeAmount = amount;public override decimal AcceptCash(decimal originalAmount)=> originalAmount + _surchargeAmount;
}

3. 上下文类

然后,创建一个上下文类来管理策略:

public class CashContext
{private CashStrategy _strategy;public void SetStrategy(CashStrategy strategy) => _strategy = strategy;public decimal GetResult(decimal money) => _strategy.AcceptCash(money);
}

4. 组合策略实现

策略模式的一个强大扩展是组合策略模式,它可以将多个策略组合使用:

public class CompositeCash : CashStrategy
{private readonly List<CashStrategy> _strategies;private readonly ExecutionOrder _order;public enum ExecutionOrder { Sequential, Priority }public CompositeCash(List<CashStrategy> strategies, ExecutionOrder order = ExecutionOrder.Sequential){_strategies = strategies;_order = order;}public override decimal AcceptCash(decimal originalAmount){var result = originalAmount;foreach (var strategy in _strategies.OrderBy(s => _order == ExecutionOrder.Priority ? 1 : 0)){result = strategy.AcceptCash(result);}return result;}
}

使用配置文件实现策略的动态加载

在实际应用中,我们希望能够通过配置文件动态加载不同的策略,而不是硬编码。这里我们使用JSON配置文件来实现。

1. JSON配置文件

{"Strategies": [{"Name": "正常收费","Type": "CashSystem.NormalCash, CashSystem"},{"Name": "八折优惠","Type": "CashSystem.DiscountCash, CashSystem","Params": [{"Name": "rate","Value": 0.8}]},{"Name": "增收","Type": "CashSystem.RevenueGrowth, CashSystem","Params": [{"Name": "amount","Value": 100}]},{"Name": "组合策略-折上折","Type": "CashSystem.CompositeCash, CashSystem","ExecutionOrder": "Sequential","Strategies": [{"Type": "CashSystem.DiscountCash, CashSystem","Params": [{"Name": "rate","Value": 0.9}]},{"Type": "CashSystem.DiscountCash, CashSystem","Params": [{"Name": "rate","Value": 0.95}]}]}]
}

2. 配置模型类

为了支持JSON配置,我们需要创建相应的数据模型:

public class StrategiesRoot
{public List<StrategyConfig> Strategies { get; set; }
}public class StrategyConfig
{public string Name { get; set; }public string Type { get; set; }public List<ParamConfig> Params { get; set; }public string ExecutionOrder { get; set; }public List<StrategyConfig> Strategies { get; set; }
}public class ParamConfig
{public string Name { get; set; }[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]public decimal Value { get; set; }
}

3. 策略加载器和工厂

策略加载器负责从配置文件读取策略配置:

public class StrategyLoader
{public Dictionary<string, CashStrategy> LoadStrategies(){string jsonString = File.ReadAllText("Strategies.json");var options = new JsonSerializerOptions{PropertyNameCaseInsensitive = true};var strategiesRoot = JsonSerializer.Deserialize<StrategiesRoot>(jsonString, options);return strategiesRoot.Strategies.ToDictionary(s => s.Name,s => StrategyFactory.CreateStrategy(s));}
}

策略工厂负责根据配置创建具体的策略实例:

public class StrategyFactory
{public static CashStrategy CreateStrategy(StrategyConfig config){if (config.Type.StartsWith("CashSystem.CompositeCash")){// 添加程序集加载逻辑 var typeName = config.Type;var type = Type.GetType(typeName)?? throw new TypeLoadException($"无法加载类型: {typeName}");var order = Enum.Parse<CompositeCash.ExecutionOrder>(config.ExecutionOrder ?? "Sequential");// 递归创建子策略 var strategies = config.Strategies?.Select(CreateStrategy).ToList() ?? new List<CashStrategy>();return new CompositeCash(strategies, order);}else{var type = Type.GetType(config.Type);var parameters = config.Params?.ToDictionary(p => p.Name, p => p.Value)?? new Dictionary<string, decimal>();return type.Name switch{"NormalCash" => (CashStrategy)Activator.CreateInstance(type),"DiscountCash" => (CashStrategy)Activator.CreateInstance(type, parameters.Values.FirstOrDefault()),"ReturnCash" => (CashStrategy)Activator.CreateInstance(type, parameters["condition"], parameters["return"]),"RevenueGrowth" => (CashStrategy)Activator.CreateInstance(type, parameters.Values.FirstOrDefault()),_ => throw new ArgumentException("不支持的参数数量")};}}
}

4. 使用策略

最后,在客户端代码中使用这些策略:

static void Main(string[] args)
{// 组合策略调用 var context = new CashContext();var strategies = new StrategyLoader().LoadStrategies();context.SetStrategy(strategies["增收"]);// 500元消费场景计算 var amount = 500m;var result = context.GetResult(amount);Console.WriteLine($"应收金额:{result}元");
}

策略模式的优势

  1. 开闭原则:新增算法时,只需添加新的策略类和配置,无需修改现有代码。
  2. 算法封装:每个算法都被封装在独立的类中,便于单元测试和维护。
  3. 灵活切换:可以在运行时动态切换不同的算法。
  4. 配置驱动:通过配置文件管理策略,实现业务逻辑与代码分离。
  5. 组合能力:通过组合策略模式,可以将多个简单策略组合成复杂策略。

策略模式的使用场景

  1. 系统中有多种算法或行为,它们只在算法或行为上稍有不同
  2. 系统需要动态地在几种算法中选择一种
  3. 算法涉及复杂的条件语句,通过策略模式可以消除条件语句
  4. 需要屏蔽算法的具体实现,只暴露它的接口

结语

策略模式通过将算法封装到独立的类中,使得算法可以独立于使用它的客户端而变化。在本例中,我们通过一个收银系统展示了策略模式的实现,并结合JSON配置文件实现了策略的动态加载和组合。这种方式使得系统更加灵活、可扩展,同时也更容易测试和维护。

通过配置文件驱动策略的选择和参数设置,我们可以在不修改代码的情况下,轻松地添加、修改和组合各种收费策略,这对于需要频繁变更业务规则的系统尤为重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/77553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十四章-PHP与HTTP协议

第十四章-PHP与HTTP协议 一&#xff0c;HTTP 协议详解 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是互联网上应用最广泛的协议之一&#xff0c;用于客户端&#xff08;如浏览器&#xff09;与服务器之间的通信。它是 Web 技术的基石…

刀客独家 | 潘胜接管百度移动生态市场部

一、 据刀客doc向独家信源确认&#xff0c;百度移动生态事业群&#xff08;MEG&#xff09;市场部日前完成重要人事调整&#xff1a;潘胜已经接任市场负责人。 此前&#xff0c;根据雷锋网3月底的报道&#xff0c;百度云渠道生态总经理陈之若离职&#xff0c;原移动生态事业群…

Springoot、Flowable快速学习

应用背景&#xff1a; 公司打算做个考勤系统&#xff0c;涉及到请假、补卡之类的流程审批。想到了工作流&#xff0c;gitee、github上看了下开源的&#xff0c;有自研的和常见的Flowable‌、Activiti‌。首先放弃自研的&#xff0c;考虑到成熟度、社区生态&#xff0c;最后选择…

关于 【Spring Boot Configuration Annotation Processor 未配置问题】 的详细分析、解决方案及代码示例

以下是关于 Spring Boot Configuration Annotation Processor 未配置问题 的详细分析、解决方案及代码示例&#xff1a; 1. 问题描述 当使用 Spring Boot 的配置注解&#xff08;如 ConfigurationProperties、Value、ConditionalOnProperty 等&#xff09;时&#xff0c;若未…

Spring系列四:AOP切面编程 第一部分

AOP切面编程 &#x1f497;AOP-官方文档&#x1f35d;AOP 讲解&#x1f35d;AOP APIs &#x1f497;动态代理&#x1f35d;初探动态代理&#x1f35d;动态代理深入&#x1f35d;AOP问题提出&#x1f4d7;使用土方法解决&#x1f4d7; 对土方法解耦-开发最简单的AOP类&#x1f4…

【云计算】云计算中IaaS、PaaS、SaaS介绍

0 随着云计算、大数据、人工智能发展迅速,布局“云”已经是互联网企业共识。云计算的服务类型分为三种,分别为IaaS、PaaS、SaaS,这三个分别是什么意思,今天做一个简单的介绍和了解。 一、云计算 云计算是用户需求通过Internet获取计算资源,把计算资源包装成服务,提供给…

使用HYPRE库并行装配IJ稀疏矩阵指南: 矩阵预分配和重复利用

使用HYPRE库并行装配IJ稀疏矩阵指南 HYPRE是一个流行的并行求解器库&#xff0c;特别适合大规模稀疏线性系统的求解。下面介绍如何并行装配IJ格式的稀疏矩阵&#xff0c;包括预先分配矩阵空间和循环使用。 1. 初始化矩阵 首先需要创建并初始化一个IJ矩阵&#xff1a; #incl…

目标跟踪最新文章阅读列表

AAAI2025 TrackFormer: Multi-Object Tracking with Transformers 论文:https://arxiv.org/abs/2101.02702 代码:https://github.com/timmeinhardt/trackformer AAAI2025 SUTrack 单目标跟踪 论文:https://pan.baidu.com/s/10cR4tQt3lSH5V2RNf7-3gg?pwd=pks2 代码:htt…

分布式GPU上计算长向量模的方法

分布式GPU上计算长向量模的方法 当向量分布在多个GPU卡上时&#xff0c;计算向量模(2-范数)需要以下步骤&#xff1a; 在每个GPU上计算本地数据的平方和跨GPU通信汇总所有平方和在根GPU上计算总和的平方根 实现方法 下面是一个完整的CUDA示例代码&#xff0c;使用NCCL进行多…

高并发下单库存扣减异常?飞算 JavaAI 自动化生成分布式事务解决方案

在电商、旅游等行业业务量激增&#xff0c;高并发下单场景中&#xff0c;传统库存扣减方式弊端尽显。超卖问题因缺乏有效并发控制机制频发&#xff0c;多个订单同时访问库存数据&#xff0c;导致同一商品多次售出&#xff0c;订单无法履约引发客户投诉&#xff1b;同时&#xf…

MVCWebAPI使用FromBody接受对象的方法

近期在做软件升级操作的时候突然想着需要的参数比较多&#xff0c;如果需要参数的话参数比较多&#xff0c;所有想着使用frombody来集合数据统一操作做了个样张希望对您有帮助 代码如下&#xff1a; /// <summary>/// 入口当前文件接口下的操作数据/// </summary>/…

Atlas 800I A2 离线部署 DeepSeek-R1-Distill-Llama-70B

一、环境信息 1.1、硬件信息 Atlas 800I A2 1.2、环境信息 注意&#xff1a;这里驱动固件最好用商业版&#xff0c;我这里用的社区版有点小问题 操作系统&#xff1a;openEuler 22.03 LTS NPU驱动&#xff1a;Ascend-hdk-910b-npu-driver_24.1.rc3_linux-aarch64.run NPU固…

NLP预处理:如何 处理表情符号

一、说明 本系列文总结了在NLP处理中&#xff0c;进行文本预处理的一些内容、步骤、处理工具包应用。本篇专门谈论网上文章表情符号处理&#xff0c;对于初学者具有深刻学习和实验指导意义。 二、介绍 表情符号已成为现代交流不可或缺的一部分&#xff0c;尤其是在社交媒体、…

C++/SDL 进阶游戏开发 —— 双人塔防(代号:村庄保卫战 14)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 二…

解锁空间数据新质生产力暨:AI(DeepSeek、ChatGPT)、Python、ArcGIS Pro多技术融合下的空间数据分析、建模与科研绘图及论文写作

人工智能&#xff08;AI&#xff09;与ArcGIS Pro的结合&#xff0c;为空间数据处理和分析开辟了前所未有的创新路径。AI通过强大的数据挖掘、深度学习及自动化能力&#xff0c;可高效处理海量、多源、异构的空间数据&#xff0c;极大提升了分析效率与决策支持能力。而ArcGIS P…

18.2.go语言redis中使用lua脚本

在 Redis 中使用 Lua 脚本可以实现原子性操作、减少网络开销以及提高执行效率。 Redis 执行 Lua 脚本的原理 Redis 内置了 Lua 解释器&#xff0c;能够直接在服务器端执行 Lua 脚本。当执行 Lua 脚本时&#xff0c;Redis 会将脚本作为一个整体执行&#xff0c;保证脚本执行期…

⭐Unity_Demolition Media Hap (播放Hap格式视频 超16K大分辨率视频 流畅播放以及帧同步解决方案)

播放大分辨率视频以及实现局域网视频同步是许多开发者会遇到的需求,AVPro有一个 Ultra Edition版本,也能播放Hap格式视频,之外就是Demolition Media Hap插件啦,实测即使是 7208*3808 大分辨率的视频帧率还是能稳定在30帧,它能帮助我们轻松解决这些问题😎。 一、插件概述 …

AI大模型知识与医疗项目实践 - Java架构师面试实战

AI大模型知识与医疗项目实践 - Java架构师面试实战 本文模拟了一场互联网大厂的Java架构师面试&#xff0c;围绕AI大模型知识、工具以及其在医疗项目中的实践和趋势展开讨论。 第一轮提问 面试官&#xff1a; 马架构&#xff0c;请您介绍一下AI大模型的基本概念及其在医疗领…

Windows 的文件系统不区分大小写,Linux区分

在 Windows 系统中&#xff0c;文件系统默认是不区分大小写的。这意味着在 Windows 上&#xff0c;文件名 ui_BalanceMeasureScreenUI.h 和 ui_balancemeasurescreenui.h 被视为同一个文件。因此&#xff0c;即使你在代码中使用了不同的大小写方式来引用同一个文件&#xff0c;…

Unity 资源合理性检测

一&#xff1a;表格过度配置&#xff0c;表格资源是否在工程中存在&#xff0c;并输出不存在的资源 import pandas as pd import glob import osassets [] count 0# 遍历configs文件夹下所有xlsx文件 for file_path in glob.glob(configs/*.xlsx):count 1try:sheets pd.re…