目錄
- 一、策略模式概述
- 二、策略模式的實現
- 2.1 策略接口
- 2.2 具體策略類
- 2.3 上下文類
- 2.4 客戶端代碼
- 2.5 UML類圖
- 2.6 UML時序圖
- 三、優缺點
- 3.1 ?優點
- 3.2 ? 缺點
- 四、最佳實踐場景
- 4.1 適合場景描述
- 4.2 具體場景
- 五、擴展
- 5.1 繼承復用機制和復合策略
- 5.2 對象管理:優化策略類數量增加問題優化
- 5.2.1 策略對象數量優化:將無狀態策略設計為共享對象(使用 Flyweight 模式)
- 5.2.2 上下文負責維護策略執行所需狀態
- 5.2.3 輕量級策略推薦單例實現,重型策略考慮對象池
- 5.3 策略與上下文的通信成本優化
- 5.3.1 參數封裝
- 5.3.2 適配器模式
- 5.4 客戶端與策略解耦優化
- 六、使用建議
參考:
設計模式:可復用面向對象軟件的基礎(典藏版) - 5.9 Strategy(策略)——對象行為型模式 - 埃里克·伽瑪 - 微信讀書
項目地址:(https://github.com/Nita121388/NitasDemo/tree/main/10DesignPatterns/DesignPatterns/StrategyPattern)[策略模式]
相關標簽:行為型設計模式、算法封裝、切換、享元模式、工廠模式、
一、策略模式概述
策略模式(Strategy Pattern)是一種行為型設計模式,用于定義一系列算法,將每個算法封裝起來,并使它們可以互換使用。策略模式讓算法的變化獨立于使用算法的客戶。
核心思想:通過將算法封裝為獨立對象,實現運行時的算法選擇。
-
動機
以開發一個文本編輯器為例,其中需要實現自動換行功能。這個功能可能有多種實現方式:簡單換行(按字符數強制分割)、連字符優化換行等
現實開發中的痛點
- 擴展性差:混雜各種換行邏輯,核心功能被算法細節淹沒。當新增加新功能時,需要冒著風險修改經過測試的編輯器核心類
- 靈活性不足:當不同文檔類型需要不同換行策略時,需要復雜的條件判斷
- 復用性低:當需要為移動端定制換行策略時,不得不創建大量重復代碼
策略模式如何解決問題?
通過將換行算法抽象為獨立模塊:
- 解耦核心邏輯:文本編輯器只需關注文本處理,不關心具體換行實現
- 動態切換策略:根據文檔類型、設備類型或用戶設置,在運行時自由切換算法
- 安全擴展:新增算法只需添加新類,無需修改現有代碼(符合開閉原則)
- 清晰復用:其他需要換行功能的模塊可以直接調用策略集合
-
核心概念/角色
- 📜策略接口(Strategy):定義了所有支持算法的公共接口,上下文(Context)使用這個接口來調用具體的策略類。
- ?具體策略類(Concrete Strategy):實現了策略接口,提供具體的算法實現。
- 🔄上下文(Context):持有一個策略接口的引用,用于調用策略類的方法。上下文可以動態地改變其引用的策略對象。
-
策略模式核心價值
- 將算法封裝為獨立對象
- 實現運行時的靈活切換
- 提高系統的擴展性和維護性
關鍵洞察:策略模式不是簡單地封裝不同實現,而是通過建立清晰的算法供應鏈,讓業務邏輯與具體算法實現形成「松耦合,高內聚」的協作關系。
二、策略模式的實現
2.1 策略接口
策略接口定義了所有策略類的公共方法,客戶端通過這個接口與具體的策略類交互。
public interface IStrategy
{void Execute(int a, int b);
}
2.2 具體策略類
每個具體策略類都實現了策略接口,并提供了具體的算法實現。
public class AddStrategy : IStrategy
{public void Execute(int a, int b){Console.WriteLine($"Add: {a} + {b} = {a + b}");}
}public class SubtractStrategy : IStrategy
{public void Execute(int a, int b){Console.WriteLine($"Subtract: {a} - {b} = {a - b}");}
}public class MultiplyStrategy : IStrategy
{public void Execute(int a, int b){Console.WriteLine($"Multiply: {a} * {b} = {a * b}");}
}
2.3 上下文類
上下文類持有一個策略接口的引用,并通過這個引用調用具體的策略方法。上下文可以根據需要動態地更換策略。
public class Context
{private IStrategy _strategy;public Context(IStrategy strategy){_strategy = strategy;}public void SetStrategy(IStrategy strategy){_strategy = strategy;}public void ExecuteStrategy(int a, int b){_strategy.Execute(a, b);}
}
2.4 客戶端代碼
客戶端通過上下文類調用具體的策略方法,并可以在運行時動態更換策略。
class Program
{static void Main(string[] args){// 創建具體策略IStrategy addStrategy = new AddStrategy();IStrategy subtractStrategy = new SubtractStrategy();IStrategy multiplyStrategy = new MultiplyStrategy();// 設置初始策略Context context = new Context(addStrategy);context.ExecuteStrategy(10, 5);// 更改策略context.SetStrategy(subtractStrategy);context.ExecuteStrategy(10, 5);// 更改策略context.SetStrategy(multiplyStrategy);context.ExecuteStrategy(10, 5);}
}
2.5 UML類圖
2.6 UML時序圖
三、優缺點
3.1 ?優點
- 算法的獨立性:每個算法封裝在獨立的類中,便于維護和單元測試。
- 消除條件分支:替代了傳統方式中繁瑣的條件判斷(如
if-else
或switch-case
結構),每個策略類封裝獨立行為邏輯,使代碼更簡潔、易讀。 - 符合開閉原則:新增算法時只需添加新的策略類,無需修改現有的代碼結構,系統對擴展開放、對修改關閉。
- 動態切換能力:運行時可靈活切換不同的策略實現,無需重新編譯,系統更靈活。
- 代碼清晰與可維護性:通過上下文(Context)與策略解耦,消除算法硬編碼,使業務邏輯更清晰,減少維護成本。
3.2 ? 缺點
-
類的數量增加 : 每種策略需要一個獨立的類,可能導致類的數量過多。
-
客戶端需要了解所有策略 :客戶端需要知道不同策略的具體實現,以便選擇合適的策略。
-
可能導致過度設計 :對于簡單的場景,使用策略模式可能顯得過于復雜。
-
維護策略的上下文 :上下文需要維護與策略相關的狀態,可能增加復雜性。
在策略模式中,上下文需要知道當前使用的是哪個策略,并且在一些情況下,上下文還需要保存一些與策略相關的狀態。
舉個例子:
一個訂單處理系統,有不同的折扣策略,比如會員折扣、節日折扣等。這些策略可能會根據訂單金額來計算折扣。
- 上下文的角色:
- 上下文類需要知道當前的折扣策略是什么(比如是會員折扣還是節日折扣)。
- 它還需要保存訂單金額這個狀態,以便在計算折扣時使用。
- 復雜性問題:
- 上下文需要管理這些狀態信息,確保在切換策略時狀態是正確的。
- 如果策略比較多,或者策略之間的狀態依賴關系復雜,上下文的代碼就會變得復雜,增加了理解和維護的難度。
- 上下文的角色:
四、最佳實踐場景
4.1 適合場景描述
- 需要多種算法變體時 🧮
- 需要運行時切換算法 🔄
- 需要隔離算法邏輯與業務邏輯📦
- 消除多個條件判斷語句 🚮
4.2 具體場景
- 算法變體密集場景:
- 如加密算法(AES、DES)需根據不同安全級別靈活切換。
- 支付網關:銀聯、支付寶、微信等支付策略。
- 業務規則波動頻繁:
- 促銷活動規則(滿減、折扣、贈品等)
- 物流配送策略(標準、特快、無人機)
- 復雜算法隔離需求:
- 機器學習模型(決策樹、神經網絡、線性回歸)
- 數據處理流程(批處理、流處理、實時處理)
- 多種驗證方式:
- 不同用戶身份驗證(人臉識別、指紋識別、短信驗證碼等)
五、擴展
5.1 繼承復用機制和復合策略
- 繼承復用機制:可以通過共同的接口或抽象類提取公共邏輯,減少重復代碼,支持策略族的復用,促進算法演進。
- 復合策略擴展:支持組合多個策略應對復雜場景,如組合風險評估和稅務優化策略,增強系統可擴展性。
以下是一個結合繼承復用和復合策略擴展的C#策略模式示例,以投資決策系統場景為例:
-
策略接口(抽象策略)
// 策略接口(抽象策略) public interface IInvestmentStrategy {void Execute(Portfolio portfolio); }
-
抽象策略基類(繼承復用)
- 封裝公共驗證邏輯
ValidatePortfolio
- 強制子類實現核心算法
Execute
- 支持統一添加日志、監控等橫切關注點
- 典型應用:所有具體策略繼承公共驗證和日志邏輯
// 抽象策略基類(繼承復用) public abstract class CommonStrategy : IInvestmentStrategy {// 公共驗證方法protected void ValidatePortfolio(Portfolio portfolio){if (portfolio.TotalValue <= 0)throw new ArgumentException("Invalid portfolio value");}// 抽象方法要求子類必須實現public abstract void Execute(Portfolio portfolio); }
- 封裝公共驗證邏輯
-
具體策略
-
具體策略1:風險評估策略(繼承復用公共邏輯)
// 具體策略1:風險評估策略(繼承復用公共邏輯) public class RiskAssessmentStrategy : CommonStrategy {public override void Execute(Portfolio portfolio){ValidatePortfolio(portfolio); // 復用基類驗證// 具體風險計算邏輯portfolio.RiskLevel = CalculateRisk(portfolio.Assets);Console.WriteLine($"風險評估完成,當前風險等級:{portfolio.RiskLevel}");}private RiskLevel CalculateRisk(IEnumerable<Asset> assets){// 實際風險計算邏輯return RiskLevel.Moderate;} }
-
具體策略2:稅務優化策略(繼承復用公共邏輯)
// 具體策略2:稅務優化策略(繼承復用公共邏輯) public class TaxOptimizationStrategy : CommonStrategy {public override void Execute(Portfolio portfolio){ValidatePortfolio(portfolio); // 復用基類驗證// 具體稅務優化邏輯portfolio.TaxBurden = OptimizeTax(portfolio.Investments);Console.WriteLine($"稅務優化完成,稅務負擔減少:{portfolio.TaxBurden}%");}private decimal OptimizeTax(IEnumerable<Investment> investments){// 實際稅務優化邏輯return 15.5m;} }
-
具體策略3:流動性分析策略(繼承復用公共邏輯)
#region - 具體策略3:流動性分析策略(繼承復用公共邏輯) public class LiquidityAnalysisStrategy : CommonStrategy {public override void Execute(Portfolio portfolio){ValidatePortfolio(portfolio); // 復用基類驗證// 具體流動性分析邏輯portfolio.LiquidityScore = CalculateLiquidity(portfolio.Assets);Console.WriteLine($"流動性分析完成,流動性得分:{portfolio.LiquidityScore}");}private decimal CalculateLiquidity(IEnumerable<Asset> assets){// 示例流動性計算邏輯:假設流動性得分基于資產價值的加權平均decimal totalValue = assets.Sum(a => a.Value);decimal liquidityScore = assets.Sum(a => a.Value * a.LiquidityFactor) / totalValue;return liquidityScore;} } #endregion
-
-
復合策略(策略擴展)
- 動態組合多個策略(如風險+稅務+流動性分析)
- 支持策略執行順序控制(通過添加順序)
- 允許運行時動態調整策略組合
- 典型應用:構建投資策略流水線
// 復合策略(策略擴展) public class CompositeStrategy : IInvestmentStrategy {private readonly List _strategies = new();public void AddStrategy(IInvestmentStrategy strategy){_strategies.Add(strategy);}public void Execute(Portfolio portfolio){foreach (var strategy in _strategies){strategy.Execute(portfolio);}} }
-
上下文類
// 上下文類 public class PortfolioManager {private IInvestmentStrategy _strategy;public void SetStrategy(IInvestmentStrategy strategy){_strategy = strategy;}public void ExecuteStrategy(Portfolio portfolio){_strategy?.Execute(portfolio);} }
-
Client Code 、使用示例
// 使用示例 var portfolio = new Portfolio(); var manager = new PortfolioManager();// 使用單一策略 manager.SetStrategy(new RiskAssessmentStrategy()); manager.ExecuteStrategy(portfolio);// 使用復合策略 var composite = new CompositeStrategy(); composite.AddStrategy(new RiskAssessmentStrategy()); composite.AddStrategy(new TaxOptimizationStrategy()); composite.AddStrategy(new LiquidityAnalysisStrategy()); // 新增策略manager.SetStrategy(composite); manager.ExecuteStrategy(portfolio);
輸出
風險評估完成,當前風險等級:Moderate 風險評估完成,當前風險等級:Moderate 稅務優化完成,稅務負擔減少:15.5% 流動性分析完成,流動性得分:0.8666666666666666666666666667
-
關鍵設計說明
- 策略族的統一管理(通過接口和抽象類)
- 算法實現的隔離變化(具體策略獨立演進)
- 復雜業務場景的靈活組合(復合策略)
- 公共能力的集中維護(驗證、日志等)
- 符合開閉原則
- 新增策略只需實現
IInvestmentStrategy
- 策略演進不影響客戶端代碼
- 復合策略可以嵌套其他復合策略
- 支持策略優先級配置(通過修改復合策略實現)
- 新增策略只需實現
5.2 對象管理:優化策略類數量增加問題優化
5.2.1 策略對象數量優化:將無狀態策略設計為共享對象(使用 Flyweight 模式)
場景:假設有一個圖像處理系統,支持多種圖像濾鏡(如黑白濾鏡、模糊濾鏡、銳化濾鏡等)。這些濾鏡是無狀態的,即它們的行為不依賴于任何內部狀態。
優化方式:使用 Flyweight 模式將無狀態策略設計為共享對象。
-
定義策略接口
// 定義策略接口 public interface IFilterStrategy {void ApplyFilter(string image); }
-
具體策略類:黑白濾鏡
// 具體策略類:黑白濾鏡 public class BlackAndWhiteFilter : IFilterStrategy {public void ApplyFilter(string image){Console.WriteLine($"Applying Black and White filter to {image}");} }
-
具體策略類:模糊濾鏡
// 具體策略類:模糊濾鏡 public class BlurFilter : IFilterStrategy {public void ApplyFilter(string image){Console.WriteLine($"Applying Blur filter to {image}");} }
-
策略工廠類(Flyweight 模式)
- Flyweight 模式的核心:通過一個靜態字典
_filters
來存儲已經創建的策略對象。這樣可以避免重復創建相同類型的策略對象。 - 單例模式的應用:由于
_filters
是靜態的,FilterFactory
類在整個程序中只會維護一份策略對象的映射關系。 - 策略對象的復用:當客戶端請求某個類型的濾鏡時,
GetFilter
方法會先檢查_filters
是否已經存在該類型的策略對象。如果不存在,則創建一個新的策略對象并存儲在字典中;如果已經存在,則直接返回已有的對象。 - 優化效果:通過這種方式,系統中每種類型的策略對象只會被創建一次,大大減少了對象的實例化數量,節省了內存。
// 策略工廠類(Flyweight 模式) public class FilterFactory {private static readonly Dictionary<string, IFilterStrategy> _filters = new Dictionary<string, IFilterStrategy>();public static IFilterStrategy GetFilter(string filterType){if (!_filters.ContainsKey(filterType)){switch (filterType){case "BlackAndWhite":_filters[filterType] = new BlackAndWhiteFilter();break;case "Blur":_filters[filterType] = new BlurFilter();break;default:throw new ArgumentException("Invalid filter type");}}return _filters[filterType];} }
- Flyweight 模式的核心:通過一個靜態字典
-
上下文類
// 上下文類 public class ImageProcessor {private IFilterStrategy _filterStrategy;public void SetFilterStrategy(IFilterStrategy filterStrategy){_filterStrategy = filterStrategy;}public void ProcessImage(string image){_filterStrategy.ApplyFilter(image);} }
-
Client Code
using System;// 客戶端代碼 public class Program {public static void Main(){var processor = new ImageProcessor();// 使用共享的黑白濾鏡策略對象processor.SetFilterStrategy(FilterFactory.GetFilter("BlackAndWhite"));processor.ProcessImage("image1.jpg");// 使用共享的模糊濾鏡策略對象processor.SetFilterStrategy(FilterFactory.GetFilter("Blur"));processor.ProcessImage("image2.jpg");} }
結果
Applying Black and White filter to image1.jpg Applying Blur filter to image2.jpg
5.2.2 上下文負責維護策略執行所需狀態
場景:假設我們有一個訂單處理系統,支持不同的折扣策略(如會員折扣、節日折扣等)。這些策略可能需要訪問上下文中的狀態(如訂單金額)。
優化方式:上下文負責維護策略執行所需的狀態。
-
定義策略接口
// 定義策略接口 public interface IDiscountStrategy {decimal ApplyDiscount(decimal orderAmount); }
-
具體策略類:會員折扣 和 具體策略類:節日折扣
// 具體策略類:會員折扣 public class MemberDiscount : IDiscountStrategy {public decimal ApplyDiscount(decimal orderAmount){return orderAmount * 0.9m; // 10% 折扣} }// 具體策略類:節日折扣 public class HolidayDiscount : IDiscountStrategy {public decimal ApplyDiscount(decimal orderAmount){return orderAmount * 0.8m; // 20% 折扣} }
-
上下文類
public class OrderProcessor {private IDiscountStrategy _discountStrategy; // 當前使用的折扣策略private decimal _orderAmount; // 訂單金額,策略執行所需的狀態public OrderProcessor(decimal orderAmount){_orderAmount = orderAmount; // 初始化訂單金額}public void SetDiscountStrategy(IDiscountStrategy discountStrategy){_discountStrategy = discountStrategy; // 設置當前的折扣策略}public decimal ProcessOrder(){return _discountStrategy.ApplyDiscount(_orderAmount); // 調用策略對象的 ApplyDiscount 方法} }
OrderProcessor
類是上下文類,它負責維護訂單金額_orderAmount
,并提供一個方法來設置折扣策略_discountStrategy
。它的主要職責是:- 維護狀態:保存訂單金額
_orderAmount
,這是策略執行時需要使用的狀態。 - 策略切換:通過
SetDiscountStrategy
方法動態設置不同的折扣策略。 - 執行策略:在
ProcessOrder
方法中調用當前設置的折扣策略的ApplyDiscount
方法,并將訂單金額傳遞給策略對象。
- 維護狀態:保存訂單金額
-
Client
var order = new OrderProcessor(100m);// 使用會員折扣策略order.SetDiscountStrategy(new MemberDiscount());Console.WriteLine($"Member Discount: {order.ProcessOrder()}");// 使用節日折扣策略order.SetDiscountStrategy(new HolidayDiscount());Console.WriteLine($"Holiday Discount: {order.ProcessOrder()}");
解釋:OrderProcessor
上下文類負責維護訂單金額,并將其傳遞給策略對象。這樣,策略對象不需要自己維護狀態,減少了類的復雜性。
5.2.3 輕量級策略推薦單例實現,重型策略考慮對象池
場景:假設我們有一個日志系統,支持不同的日志輸出策略(如控制臺輸出、文件輸出等)。控制臺輸出策略是輕量級的,而文件輸出策略是資源密集型的。
優化方式:輕量級策略使用單例模式,重型策略使用對象池。
-
策略接口定義
public interface ILogStrategy {void Log(string message); }
-
具體策略類:控制臺輸出策略(單例模式)
說明:
- 實現了
ILogStrategy
接口,用于將日志輸出到控制臺。 - 使用了單例模式,確保整個程序中只有一個
ConsoleLogStrategy
實例。 Log
方法將日志信息以控制臺輸出的形式展示。
public class ConsoleLogStrategy : ILogStrategy {private static readonly ConsoleLogStrategy _instance = new ConsoleLogStrategy();private ConsoleLogStrategy() { }public static ConsoleLogStrategy Instance => _instance;public void Log(string message){Console.WriteLine($"Console Log: {message}");} }
- 實現了
-
具體策略類:文件輸出策略
public class FileLogStrategy : ILogStrategy {public void Log(string message){Console.WriteLine($"File Log: {message}");} }
-
對象池管理文件輸出策略
說明:
- 使用對象池模式管理
FileLogStrategy
實例。 - 靜態構造函數初始化了對象池,預先創建了5個
FileLogStrategy
實例。 Acquire
方法用于從池中獲取一個實例,如果池為空,則創建新的實例。Release
方法將使用完畢的實例放回對象池,以便復用。
public class FileLogStrategyPool {private static readonly Queue _pool = new Queue();static FileLogStrategyPool(){for (int i = 0; i < 5; i++){_pool.Enqueue(new FileLogStrategy());}}public static FileLogStrategy Acquire(){if (_pool.Count > 0){return _pool.Dequeue();}return new FileLogStrategy();}public static void Release(FileLogStrategy strategy){_pool.Enqueue(strategy);} }
- 使用對象池模式管理
-
上下文類:日志記錄器
說明:
Logger
類是策略模式的上下文類,用于封裝日志記錄的邏輯。- 客戶端可以通過
SetLogStrategy
方法動態設置日志策略。 Log
方法調用當前策略的Log
方法,實現日志記錄。
public class Logger {private ILogStrategy _logStrategy;public void SetLogStrategy(ILogStrategy logStrategy){_logStrategy = logStrategy;}public void Log(string message){_logStrategy.Log(message);} }
-
客戶端代碼
說明:
- 方式1:使用單例的
ConsoleLogStrategy
將日志輸出到控制臺。 - 方式2:從對象池中獲取一個
FileLogStrategy
實例,將日志輸出到文件,并在使用完畢后將其釋放回對象池。
var logger = new Logger();// 使用單例的控制臺輸出策略 logger.SetLogStrategy(ConsoleLogStrategy.Instance); logger.Log("This is a log message.");// 使用對象池的文件輸出策略 var fileLogStrategy = FileLogStrategyPool.Acquire(); logger.SetLogStrategy(fileLogStrategy); logger.Log("This is a file log message."); FileLogStrategyPool.Release(fileLogStrategy);
輸出
Console Log: This is a log message. File Log: This is a file log message.
- 方式1:使用單例的
解釋:ConsoleLogStrategy
使用單例模式來減少內存消耗,而 FileLogStrategy
使用對象池來管理資源密集型對象的生命周期,避免頻繁的創建與銷毀。
5.3 策略與上下文的通信成本優化
當不同策略需要不同參數時,上下文需處理多種參數類型,導致接口復雜且類型不安全。
5.3.1 參數封裝
將參數封裝到統一對象中,減少接口復雜性。
-
參數封裝類用于存儲支付過程中需要的參數信息。
說明:
此部分將支付相關的參數封裝到一個類中,方便在不同支付策略中傳遞和使用。
這種封裝方式提高了代碼的可維護性和可擴展性。
public class PaymentParameters {public string CardNumber { get; set; } // 信用卡卡號public string SecurityCode { get; set; } // 信用卡安全碼public string PhoneNumber { get; set; } // 支付寶綁定的手機號 }
-
策略接口:
IPaymentStrategy
- 策略模式的核心是定義一個通用接口,讓不同的策略類實現該接口。
- 這里定義了一個
ProcessPayment
方法,所有具體的支付策略類(如信用卡支付、支付寶支付)都必須實現該方法。
public interface IPaymentStrategy {void ProcessPayment(double amount, PaymentParameters parameters); // 定義支付方法 }
-
具體策略類
-
信用卡支付策略:
CreditCardStrategy
說明:
- 這是信用卡支付的具體實現。
- 在支付前,會檢查信用卡號和安全碼是否為空。如果為空,拋出異常。
- 如果信息完整,則輸出支付信息。
public class CreditCardStrategy : IPaymentStrategy {public void ProcessPayment(double amount, PaymentParameters parameters){if (string.IsNullOrEmpty(parameters.CardNumber) || string.IsNullOrEmpty(parameters.SecurityCode)){throw new ArgumentException("信用卡信息缺失");}Console.WriteLine($"信用卡支付:金額={amount}, 卡號={parameters.CardNumber}, 安全碼={parameters.SecurityCode}");} }
-
支付寶支付策略:
AlipayStrategy
說明:
- 這是支付寶支付的具體實現。
- 在支付前,會檢查手機號是否為空。如果為空,拋出異常。
- 如果手機號有效,則輸出支付信息。
public class AlipayStrategy : IPaymentStrategy {public void ProcessPayment(double amount, PaymentParameters parameters){if (string.IsNullOrEmpty(parameters.PhoneNumber)){throw new ArgumentException("手機號缺失");}Console.WriteLine($"支付寶支付:金額={amount}, 手機號={parameters.PhoneNumber}");} }
-
-
上下文類:
PaymentContext
上下文類用于動態切換支付策略,并調用對應的支付方法。
說明:
PaymentContext
類提供了一個接口來設置和執行支付策略。_strategy
屬性用于存儲當前的支付策略。SetStrategy
方法用于動態切換支付策略。ExecutePayment
方法調用當前策略的ProcessPayment
方法,完成支付操作。
public class PaymentContext {private IPaymentStrategy _strategy; // 當前使用的支付策略public void SetStrategy(IPaymentStrategy strategy){_strategy = strategy; // 設置當前支付策略}public void ExecutePayment(double amount, PaymentParameters parameters){_strategy?.ProcessPayment(amount, parameters); // 調用當前策略的支付方法} }
-
Client Code
說明:
- 創建了
PaymentParameters
參數對象并設置了支付參數。 - 創建了
PaymentContext
對象,并通過SetStrategy
方法動態切換支付策略。 - 調用
ExecutePayment
方法執行支付操作。 - 通過切換策略,可以輕松地在不同支付方式之間切換,展示了策略模式的靈活性。
public class Program {public static void Main(){var parameters = new PaymentParameters{CardNumber = "1234-5678-9012-3456", // 信用卡卡號SecurityCode = "123", // 信用卡安全碼PhoneNumber = "13812345678" // 支付寶綁定的手機號};var context = new PaymentContext();// 使用信用卡支付context.SetStrategy(new CreditCardStrategy());context.ExecutePayment(100.0, parameters);// 切換為支付寶支付context.SetStrategy(new AlipayStrategy());context.ExecutePayment(200.0, parameters);} }
輸出
信用卡支付:金額=100, 卡號=1234-5678-9012-3456, 安全碼=123 支付寶支付:金額=200, 手機號=13812345678
- 創建了
5.3.2 適配器模式
通過適配器將不同參數轉換為通用格式,簡化交互。
-
通用參數接口(
IPaymentParameters
)-
說明:
定義了一個通用接口,用于獲取支付參數。
GetParameter<T>
方法通過泛型和鍵值對的方式,允許不同類型的支付方式提供參數訪問功能。
public interface IPaymentParameters {T GetParameter<T>(string key); }
-
-
參數適配器
-
信用卡參數適配器(
CreditCardAdapter
)- 功能說明:
- 信用卡支付需要提供卡號和安全碼。
- 通過
GetParameter<T>
方法,根據鍵值返回對應的參數。
public class CreditCardAdapter : IPaymentParameters {private readonly string _cardNumber;private readonly string _securityCode;public CreditCardAdapter(string cardNumber, string securityCode){_cardNumber = cardNumber;_securityCode = securityCode;}public T GetParameter<T>(string key){switch (key){case "CardNumber": return (T)(object)_cardNumber;case "SecurityCode": return (T)(object)_securityCode;default: throw new KeyNotFoundException($"參數 {key} 不存在");}} }
- 功能說明:
-
支付寶參數適配器(
AlipayAdapter
)- 功能說明:
- 支付寶支付需要提供手機號。
- 通過
GetParameter<T>
方法,根據鍵值返回對應的參數。
public class AlipayAdapter : IPaymentParameters {private readonly string _phoneNumber;public AlipayAdapter(string phoneNumber){_phoneNumber = phoneNumber;}public T GetParameter<T>(string key){if (key == "PhoneNumber") return (T)(object)_phoneNumber;throw new KeyNotFoundException($"參數 {key} 不存在");} }
- 功能說明:
-
-
策略接口(
IPaymentStrategy
)- 功能說明:
- 定義了支付策略的通用接口。
ProcessPayment
方法用于處理支付邏輯,接受金額和支付參數。
public interface IPaymentStrategy {void ProcessPayment(double amount, IPaymentParameters parameters); }
- 功能說明:
-
具體策略類
-
信用卡支付策略(
CreditCardStrategy
)- 功能說明:
- 從參數適配器中獲取卡號和安全碼。
- 打印信用卡支付的詳細信息。
public class CreditCardStrategy : IPaymentStrategy {public void ProcessPayment(double amount, IPaymentParameters parameters){string cardNumber = parameters.GetParameter<string>("CardNumber");string securityCode = parameters.GetParameter<string>("SecurityCode");Console.WriteLine($"信用卡支付:金額={amount}, 卡號={cardNumber}, 安全碼={securityCode}");} }
- 功能說明:
-
支付寶支付策略(
AlipayStrategy
)- 功能說明:
- 從參數適配器中獲取手機號。
- 打印支付寶支付的詳細信息。
public class AlipayStrategy : IPaymentStrategy {public void ProcessPayment(double amount, IPaymentParameters parameters){string phone = parameters.GetParameter<string>("PhoneNumber");Console.WriteLine($"支付寶支付:金額={amount}, 手機號={phone}");} }
- 功能說明:
-
-
上下文類(
PaymentContext
)- 功能說明:
- 管理具體的支付策略。
- 通過
SetStrategy
方法設置支付策略。 - 通過
ExecutePayment
方法執行支付操作。
public class PaymentContext {private IPaymentStrategy _strategy;public void SetStrategy(IPaymentStrategy strategy){_strategy = strategy;}public void ExecutePayment(double amount, IPaymentParameters parameters){_strategy?.ProcessPayment(amount, parameters);} }
- 功能說明:
-
使用示例(
Program
類)- 功能說明:
- 創建支付上下文。
- 設置不同的支付策略并執行支付操作。
- 演示了如何通過策略模式切換不同的支付方式。
public class Program {public static void Main(){var context = new PaymentContext();// 信用卡支付var creditCardParams = new CreditCardAdapter("1234-5678-9012-3456", "123");context.SetStrategy(new CreditCardStrategy());context.ExecutePayment(100.0, creditCardParams);// 支付寶支付var alipayParams = new AlipayAdapter("13812345678");context.SetStrategy(new AlipayStrategy());context.ExecutePayment(200.0, alipayParams);} }
- 功能說明:
-
類圖
-
時序圖
5.4 客戶端與策略解耦優化
-
在傳統的策略模式中,客戶端需要了解所有策略類的具體實現細節,并負責選擇合適的策略。然而,為了實現一個靈活的支付策略模式,我們可以通過解耦客戶端代碼與具體策略的方式,使新增支付方式時只需注冊新的策略,而無需修改客戶端代碼。具體實現方式如下:
- 引入工廠模式:使用工廠模式來創建策略對象,客戶端只需知道工廠接口,而無需了解具體的策略實現。
- 策略注冊與動態選擇:通過注冊機制,允許策略在運行時動態選擇,客戶端只需提供策略標識或條件,而不需了解具體的策略實現。
- 使用配置文件:通過配置文件或注解來管理策略的選擇,客戶端可以通過配置而不是硬編碼來選擇策略。
-
策略接口
// 策略接口保持不變 public interface IPaymentStrategy {void ProcessPayment(decimal amount); }
-
策略工廠與注冊中心
策略工廠負責管理和注冊具體的支付策略,提供策略的獲取方式。
說明:
_strategies
是一個靜態字典,用于存儲策略的鍵值對。RegisterStrategy
方法用于將策略注冊到工廠中,確保每個策略鍵是唯一的。GetStrategy
方法通過鍵獲取對應的策略實例,如果鍵不存在則拋出異常。
// 策略工廠+注冊中心 public class PaymentStrategyFactory {private static readonly Dictionary _strategies = new();// 注冊策略public static void RegisterStrategy(string key, IPaymentStrategy strategy){if (!_strategies.ContainsKey(key)){_strategies.Add(key, strategy);}}// 獲取策略public static IPaymentStrategy GetStrategy(string key){if (_strategies.TryGetValue(key, out var strategy)){return strategy;}throw new KeyNotFoundException($"未找到支付策略:{key}");} }
-
配置中心
配置中心模擬了外部配置文件(如
appsettings.json
),用于定義支付策略的映射關系。說明:
PaymentStrategies
是一個字典,定義了支付類型與具體策略的映射關系。- 例如,
CreditCard
映射到CreditCardPayment
,PayPal
映射到PayPalPayment
。 - 還定義了一個默認策略(
Default
),用于處理未明確指定的支付類型。
// 配置中心(模擬appsettings.json) public static class AppConfig {public static readonly Dictionary<string, string> PaymentStrategies = new(){{ "CreditCard", "CreditCardPayment" },{ "PayPal", "PayPalPayment" },{ "Default", "CreditCard" }}; }
-
初始化注冊
在程序啟動時,需要將具體的支付策略注冊到工廠中。
說明:
- 這里注冊了兩種支付策略:
CreditCardPayment
和PayPalPayment
。 - 這些策略需要實現
IPaymentStrategy
接口,并在程序啟動時完成注冊。
// 初始化注冊(通常在程序啟動時) PaymentStrategyFactory.RegisterStrategy("CreditCardPayment", new CreditCardPayment()); PaymentStrategyFactory.RegisterStrategy("PayPalPayment", new PayPalPayment());
- 這里注冊了兩種支付策略:
-
客戶端代碼 Client Code
客戶端代碼實現了支付邏輯的調用,通過配置中心和策略工廠獲取具體的支付策略。
說明:
- 客戶端通過傳入的
paymentType
,從配置中心獲取對應的策略鍵。 - 如果未找到匹配的策略鍵,則使用默認策略。
- 使用策略工廠獲取具體的策略實例,并調用其
ProcessPayment
方法完成支付。
// 優化后的客戶端 class OptimizedClient {public void Checkout(string paymentType){// 通過配置映射獲取實際策略keyvar strategyKey = AppConfig.PaymentStrategies.TryGetValue(paymentType, out var key) ? key : AppConfig.PaymentStrategies["Default"];var strategy = PaymentStrategyFactory.GetStrategy(strategyKey);strategy.ProcessPayment(100);} }
輸出
Processing Credit Card payment for amount: 100 Processing PayPal payment for amount: 100
- 客戶端通過傳入的
-
類圖
-
時序圖
六、使用建議
?? 注意事項:
- 客戶端需理解策略間的功能差異
- 避免過度設計簡單算法場景
- 策略接口設計應保持適度抽象
- 權衡模式引入的架構復雜度
- 當存在多個相似類僅在行為不同時優先考慮
- 配合工廠模式使用可以更好地管理策略對象
- 注意控制策略類的數量膨脹