C#策略模式实现物流价格计算引擎

C#策略模式实现物流价格计算引擎 1. 项目概述物流价格计算引擎的设计初衷物流行业的价格计算一直是业务系统的核心模块。不同物流公司、不同运输方式、不同客户等级都会影响最终报价。传统硬编码的方式会导致代码臃肿、维护困难这正是策略模式大显身手的地方。我在开发某跨境物流系统时就遇到了这样的需求系统需要支持20种计价规则包括按重量、体积、件数、特殊商品类型等不同维度计算还要考虑节假日溢价、会员折扣等复杂因素。策略模式完美解决了这个问题让我们的价格引擎既灵活又易于扩展。2. 策略模式核心原理与物流场景适配2.1 策略模式的三大核心组件策略模式包含三个关键部分策略接口定义所有计价算法必须实现的方法具体策略类实现不同计价算法如重量计价、体积计价等上下文类维护策略引用提供统一调用接口在物流场景中我们可以这样映射策略接口 → IShippingPriceStrategy具体策略 → WeightBasedStrategy, VolumeBasedStrategy等上下文 → PriceCalculationEngine2.2 物流计价的特点与策略选择物流计价有几个显著特征算法多样性同一批货物可能有多种计价方式动态切换需求根据客户合同随时切换计算规则扩展频繁经常需要新增特殊计价规则策略模式的价值在于将算法封装成独立类符合单一职责原则运行时动态切换算法无需修改客户端代码新增算法不影响现有代码符合开闭原则3. C#实现物流价格引擎的完整方案3.1 基础架构设计首先定义策略接口public interface IShippingPriceStrategy { decimal Calculate(ShippingContext context); }然后实现具体策略比如重量计价public class WeightBasedStrategy : IShippingPriceStrategy { public decimal Calculate(ShippingContext context) { decimal basePrice context.Weight * context.UnitPrice; // 超重附加费计算逻辑 if(context.Weight 50) { basePrice (context.Weight - 50) * 5; } return basePrice; } }上下文类的典型实现public class PriceCalculator { private IShippingPriceStrategy _strategy; public void SetStrategy(IShippingPriceStrategy strategy) { _strategy strategy; } public decimal ExecuteCalculation(ShippingContext context) { return _strategy?.Calculate(context) ?? throw new InvalidOperationException(Strategy not set); } }3.2 策略的注册与获取在实际项目中我们通常使用依赖注入来管理策略。以下是ASP.NET Core中的典型配置services.AddTransientWeightBasedStrategy(); services.AddTransientVolumeBasedStrategy(); // 其他策略注册... services.AddSingletonFuncstring, IShippingPriceStrategy(serviceProvider key { return key switch { Weight serviceProvider.GetServiceWeightBasedStrategy(), Volume serviceProvider.GetServiceVolumeBasedStrategy(), _ throw new KeyNotFoundException() }; });使用时通过工厂获取策略public class PricingService { private readonly Funcstring, IShippingPriceStrategy _strategyFactory; public PricingService(Funcstring, IShippingPriceStrategy strategyFactory) { _strategyFactory strategyFactory; } public decimal CalculatePrice(string strategyKey, ShippingContext context) { var strategy _strategyFactory(strategyKey); return strategy.Calculate(context); } }4. 高级应用场景与优化技巧4.1 复合策略的实现实际业务中经常需要组合多种计价方式。我们可以通过装饰器模式实现public class CompositeStrategy : IShippingPriceStrategy { private readonly IEnumerableIShippingPriceStrategy _strategies; public CompositeStrategy(IEnumerableIShippingPriceStrategy strategies) { _strategies strategies; } public decimal Calculate(ShippingContext context) { return _strategies.Sum(s s.Calculate(context)); } }4.2 策略缓存与性能优化频繁创建策略实例会影响性能可以考虑将策略设计为无状态类对策略实例进行缓存使用对象池管理策略实例public class StrategyPool { private readonly ConcurrentDictionaryType, object _pool new(); public T GetStrategyT() where T : new() { return (T)_pool.GetOrAdd(typeof(T), _ new T()); } }4.3 动态策略配置通过JSON配置动态定义策略规则{ Strategies: [ { Name: WeightVolumeComposite, Type: Composite, Strategies: [Weight, Volume] } ] }对应的加载代码var config LoadConfig(); foreach(var strategyConfig in config.Strategies) { if(strategyConfig.Type Composite) { var strategies strategyConfig.Strategies .Select(s _strategyFactory(s)) .ToList(); _compositeStrategies.Add(strategyConfig.Name, new CompositeStrategy(strategies)); } }5. 实战中的经验与避坑指南5.1 策略选择的常见问题问题1策略选择逻辑过于分散反模式在多个地方硬编码策略选择逻辑 解决方案集中策略选择逻辑可以使用工厂模式问题2策略参数传递混乱反模式通过构造函数传递所有可能参数 解决方案使用上下文对象封装所有参数5.2 性能优化实测数据我们对100万次计算进行了基准测试策略类型原始实现(ms)优化后(ms)重量策略1200450体积策略1500500复合策略3200900优化手段避免在策略中new对象预计算不变参数使用Span 处理数组5.3 调试技巧技巧1策略追踪public class LoggingStrategyDecorator : IShippingPriceStrategy { private readonly IShippingPriceStrategy _inner; private readonly ILogger _logger; public LoggingStrategyDecorator(IShippingPriceStrategy inner, ILogger logger) { _inner inner; _logger logger; } public decimal Calculate(ShippingContext context) { _logger.LogInformation($Executing {_inner.GetType().Name}); var result _inner.Calculate(context); _logger.LogInformation($Result: {result}); return result; } }技巧2策略验证public class NullCheckStrategyDecorator : IShippingPriceStrategy { private readonly IShippingPriceStrategy _inner; public NullCheckStrategyDecorator(IShippingPriceStrategy inner) { _inner inner ?? throw new ArgumentNullException(nameof(inner)); } public decimal Calculate(ShippingContext context) { if(context null) throw new ArgumentNullException(nameof(context)); return _inner.Calculate(context); } }6. 测试策略与质量保障6.1 单元测试设计每个策略都应该有独立的测试用例[TestClass] public class WeightStrategyTests { [TestMethod] public void Calculate_Under50kg_ReturnsBasePrice() { // Arrange var strategy new WeightBasedStrategy(); var context new ShippingContext { Weight 10, UnitPrice 2 }; // Act var result strategy.Calculate(context); // Assert Assert.AreEqual(20, result); } [TestMethod] public void Calculate_Over50kg_AddsSurcharge() { // Arrange var strategy new WeightBasedStrategy(); var context new ShippingContext { Weight 60, UnitPrice 2 }; // Act var result strategy.Calculate(context); // Assert Assert.AreEqual(120 50, result); // 基础价格超重附加费 } }6.2 集成测试方案测试策略工厂和复合策略[TestClass] public class PricingIntegrationTests { private IServiceProvider _serviceProvider; [TestInitialize] public void Setup() { var services new ServiceCollection(); // 注册所有策略和工厂 // ... _serviceProvider services.BuildServiceProvider(); } [TestMethod] public void Factory_ReturnsCorrectStrategy() { var factory _serviceProvider.GetServiceFuncstring, IShippingPriceStrategy(); var strategy factory(Weight); Assert.IsInstanceOfType(strategy, typeof(WeightBasedStrategy)); } }6.3 性能测试要点重点关注策略切换的开销高并发下的线程安全内存使用情况使用BenchmarkDotNet进行测试[MemoryDiagnoser] public class StrategyBenchmarks { private PriceCalculator _calculator; private ShippingContext _context; [GlobalSetup] public void Setup() { _calculator new PriceCalculator(); _context new ShippingContext { Weight 60, Volume 2 }; } [Benchmark] public decimal WeightStrategy() { _calculator.SetStrategy(new WeightBasedStrategy()); return _calculator.ExecuteCalculation(_context); } }7. 项目扩展与演进方向7.1 与规则引擎集成对于特别复杂的计价规则可以结合RulesEngine等规则引擎public class RulesEngineStrategy : IShippingPriceStrategy { private readonly RulesEngine.RulesEngine _engine; public RulesEngineStrategy() { var workflow new Workflow { Rules new ListRule { new Rule { RuleName VolumeDiscount, Expression input.Volume 10 input.CustomerLevel \VIP\, Actions new RuleActions { OnSuccess new ActionInfo { Name ApplyDiscount, Context new Dictionarystring, object { { DiscountRate, 0.1m } } } } } } }; _engine new RulesEngine.RulesEngine(new[] { workflow }); } public decimal Calculate(ShippingContext context) { var results _engine.ExecuteAllRulesAsync(Pricing, context).Result; // 处理规则执行结果... } }7.2 机器学习定价收集历史数据训练简单定价模型public class MLStrategy : IShippingPriceStrategy { private readonly PredictionEngineShippingData, ShippingPrediction _predictionEngine; public MLStrategy() { var mlContext new MLContext(); // 加载模型 var model mlContext.Model.Load(pricing_model.zip, out _); _predictionEngine mlContext.Model.CreatePredictionEngineShippingData, ShippingPrediction(model); } public decimal Calculate(ShippingContext context) { var input new ShippingData { Weight context.Weight, Distance context.Distance, // 其他特征... }; var prediction _predictionEngine.Predict(input); return (decimal)prediction.PredictedPrice; } }7.3 分布式计算支持对于超大规模计算可以将策略部署为微服务public class RemoteStrategy : IShippingPriceStrategy { private readonly HttpClient _client; public RemoteStrategy(HttpClient client) { _client client; } public async Taskdecimal CalculateAsync(ShippingContext context) { var response await _client.PostAsJsonAsync(/api/pricing/weight, context); return await response.Content.ReadFromJsonAsyncdecimal(); } }8. 项目部署与监控8.1 配置管理最佳实践建议采用以下配置结构/PricingStrategies /WeightBased config.json /VolumeBased config.json /Composite config.json使用IOptions模式加载配置services.ConfigureWeightStrategyOptions( Configuration.GetSection(PricingStrategies:WeightBased));8.2 健康检查实现为策略引擎添加健康检查services.AddHealthChecks() .AddCheckStrategyHealthCheck(pricing-strategies); public class StrategyHealthCheck : IHealthCheck { private readonly IEnumerableIShippingPriceStrategy _strategies; public StrategyHealthCheck(IEnumerableIShippingPriceStrategy strategies) { _strategies strategies; } public TaskHealthCheckResult CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken default) { foreach(var strategy in _strategies) { try { var ctx new ShippingContext(); // 测试上下文 strategy.Calculate(ctx); } catch(Exception ex) { return Task.FromResult(HealthCheckResult.Unhealthy( $Strategy {strategy.GetType().Name} failed, ex)); } } return Task.FromResult(HealthCheckResult.Healthy()); } }8.3 监控与指标使用Application Insights或Prometheus收集指标public class InstrumentedStrategy : IShippingPriceStrategy { private readonly IShippingPriceStrategy _inner; private readonly Counterdouble _counter; public InstrumentedStrategy(IShippingPriceStrategy inner, IMeterFactory meterFactory) { _inner inner; var meter meterFactory.Create(Pricing); _counter meter.CreateCounterdouble(strategy.calls); } public decimal Calculate(ShippingContext context) { _counter.Add(1, new KeyValuePairstring, object(strategy, _inner.GetType().Name)); return _inner.Calculate(context); } }9. 团队协作与代码维护9.1 代码组织建议推荐的项目结构/src /Pricing /Strategies WeightBasedStrategy.cs VolumeBasedStrategy.cs ... /Models ShippingContext.cs /Interfaces IShippingPriceStrategy.cs PriceCalculator.cs PricingService.cs /tests /Pricing.UnitTests /Strategies WeightBasedStrategyTests.cs ...9.2 代码审查要点审查策略实现时需要特别关注是否保持无状态是否正确处理边界条件性能敏感路径是否优化是否包含足够的日志9.3 文档规范为每个策略添加XML注释/// summary /// 基于重量的计价策略 /// /summary /// remarks /// 计算公式基础价格 重量 * 单价 /// 超重附加费重量 50kg 时超出部分每kg加收5元 /// /remarks public class WeightBasedStrategy : IShippingPriceStrategy { // ... }10. 项目演进与重构经验10.1 从简单实现到策略模式的重构过程初始实现通常是这样的switch-casepublic decimal CalculatePrice(string method, ShippingContext context) { switch(method) { case Weight: return context.Weight * context.UnitPrice; case Volume: return context.Volume * context.VolumePrice; // 更多case... } }重构步骤提取策略接口将每个case逻辑移到独立类创建策略工厂逐步替换原有switch-case10.2 性能优化历程我们经历的三个优化阶段第一代每次调用new策略实例优点简单缺点GC压力大第二代策略对象池优点减少对象创建缺点需要管理池第三代无状态策略参数对象优点最佳性能缺点需要重构策略接口10.3 架构演进路线典型演进路径单体应用中的策略模式独立定价服务定价微服务集群基于事件的动态定价系统每个阶段的考量因素团队规模业务复杂度性能需求部署要求