策略模式是一種行為型設計模式,它將一組算法封裝成獨立的類,使它們可以相互替換。策略模式讓算法的變化獨立于使用算法的客戶端。本文將以一個收銀系統為例,詳細介紹策略模式的實現和應用。
什么是策略模式?
策略模式定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶端。
核心組件:
- 策略接口/抽象類:定義了算法的公共接口
- 具體策略類:實現策略接口的具體算法
- 上下文類:維護一個對策略對象的引用,負責將客戶端請求委派給策略對象
收銀系統中的策略模式實現
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}元");
}
策略模式的優勢
- 開閉原則:新增算法時,只需添加新的策略類和配置,無需修改現有代碼。
- 算法封裝:每個算法都被封裝在獨立的類中,便于單元測試和維護。
- 靈活切換:可以在運行時動態切換不同的算法。
- 配置驅動:通過配置文件管理策略,實現業務邏輯與代碼分離。
- 組合能力:通過組合策略模式,可以將多個簡單策略組合成復雜策略。
策略模式的使用場景
- 系統中有多種算法或行為,它們只在算法或行為上稍有不同
- 系統需要動態地在幾種算法中選擇一種
- 算法涉及復雜的條件語句,通過策略模式可以消除條件語句
- 需要屏蔽算法的具體實現,只暴露它的接口
結語
策略模式通過將算法封裝到獨立的類中,使得算法可以獨立于使用它的客戶端而變化。在本例中,我們通過一個收銀系統展示了策略模式的實現,并結合JSON配置文件實現了策略的動態加載和組合。這種方式使得系統更加靈活、可擴展,同時也更容易測試和維護。
通過配置文件驅動策略的選擇和參數設置,我們可以在不修改代碼的情況下,輕松地添加、修改和組合各種收費策略,這對于需要頻繁變更業務規則的系統尤為重要。