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

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

什么是策略模式?

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

核心组件:

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

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

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

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

相关推荐
每次的天空9 小时前
Android学习总结之自定义view设计模式理解
android·学习·设计模式
XiaoCCCcCCccCcccC10 小时前
Linux中线程池的简单实现 -- 线程安全的日志模块,策略模式,线程池的封装设计,单例模式,饿汉式单例模式,懒汉式单例模式
linux·c语言·c++·安全·单例模式·策略模式
CoderIsArt11 小时前
MVC与MVP设计模式
设计模式·mvc
钢铁男儿16 小时前
Python 重构“策略”模式:用函数简化设计模式的实践
python·设计模式·重构
free慢1 天前
设计模式(状态模式)
设计模式·状态模式
碎梦归途1 天前
23种设计模式-行为型模式之中介者模式(Java版本)
java·jvm·设计模式·中介者模式·软件设计师
不当菜虚困1 天前
JAVA设计模式——(九)工厂模式
java·开发语言·设计模式
柴郡猫乐园1 天前
智能指针之设计模式5
开发语言·设计模式·智能指针
mooridy1 天前
设计模式 | 详解常用设计模式(六大设计原则,单例模式,工厂模式,建造者模式,代理模式)
c++·设计模式