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

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

什么是策略模式?

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

核心组件:

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

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

1. 策略抽象类

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

csharp 复制代码
public abstract class CashStrategy
{
    public abstract decimal AcceptCash(decimal originalAmount);
}

2. 具体策略实现

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

csharp 复制代码
// 正常收费策略
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. 上下文类

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

csharp 复制代码
public class CashContext
{
    private CashStrategy _strategy;

    public void SetStrategy(CashStrategy strategy) => _strategy = strategy;

    public decimal GetResult(decimal money) => _strategy.AcceptCash(money);
}

4. 组合策略实现

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

csharp 复制代码
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配置文件

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配置,我们需要创建相应的数据模型:

csharp 复制代码
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. 策略加载器和工厂

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

csharp 复制代码
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)
        );
    }
}

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

csharp 复制代码
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. 使用策略

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

csharp 复制代码
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配置文件实现了策略的动态加载和组合。这种方式使得系统更加灵活、可扩展,同时也更容易测试和维护。

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

相关推荐
T___11 小时前
从入门到放弃?带你重新认识 Headless UI
前端·设计模式
葬送的代码人生11 小时前
AI Coding→像素飞机大冒险:一个让你又爱又恨的小游戏
javascript·设计模式·ai编程
渣渣_Maxz1 天前
使用 antlr 打造 Android 动态逻辑判断能力
android·设计模式
码农颜2 天前
java 设计模式_行为型_19命令模式
java·设计模式·命令模式
蔡蓝2 天前
设计模式-迭代器模式
设计模式·迭代器模式
梦想画家2 天前
数据管道架构设计指南:5大模式与最佳实践
设计模式·数据工程·数据编排
牛奶咖啡132 天前
学习设计模式《十三》——迭代器模式
设计模式·迭代器模式·内部迭代器和外部迭代器·带迭代策略的迭代器·双向迭代器·迭代器模式的优点·何时选用迭代器模式
哆啦A梦的口袋呀2 天前
设计模式汇总
python·设计模式
在未来等你2 天前
设计模式精讲 Day 1:单例模式(Singleton Pattern)
java·设计模式·面向对象·软件架构
不会编程的小江江2 天前
【设计模式】单例模式
单例模式·设计模式