策略模式是一种行为型设计模式,它将一组算法封装成独立的类,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。本文将以一个收银系统为例,详细介绍策略模式的实现和应用。
什么是策略模式?
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户端。
核心组件:
- 策略接口/抽象类:定义了算法的公共接口
- 具体策略类:实现策略接口的具体算法
- 上下文类:维护一个对策略对象的引用,负责将客户端请求委派给策略对象
收银系统中的策略模式实现
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}元");
}
策略模式的优势
- 开闭原则:新增算法时,只需添加新的策略类和配置,无需修改现有代码。
- 算法封装:每个算法都被封装在独立的类中,便于单元测试和维护。
- 灵活切换:可以在运行时动态切换不同的算法。
- 配置驱动:通过配置文件管理策略,实现业务逻辑与代码分离。
- 组合能力:通过组合策略模式,可以将多个简单策略组合成复杂策略。
策略模式的使用场景
- 系统中有多种算法或行为,它们只在算法或行为上稍有不同
- 系统需要动态地在几种算法中选择一种
- 算法涉及复杂的条件语句,通过策略模式可以消除条件语句
- 需要屏蔽算法的具体实现,只暴露它的接口
结语
策略模式通过将算法封装到独立的类中,使得算法可以独立于使用它的客户端而变化。在本例中,我们通过一个收银系统展示了策略模式的实现,并结合JSON配置文件实现了策略的动态加载和组合。这种方式使得系统更加灵活、可扩展,同时也更容易测试和维护。
通过配置文件驱动策略的选择和参数设置,我们可以在不修改代码的情况下,轻松地添加、修改和组合各种收费策略,这对于需要频繁变更业务规则的系统尤为重要。