設計模式|策略模式 Strategy Pattern 詳解

目錄

  • 一、策略模式概述
  • 二、策略模式的實現
    • 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)是一種行為型設計模式,用于定義一系列算法,將每個算法封裝起來,并使它們可以互換使用。策略模式讓算法的變化獨立于使用算法的客戶。

核心思想:通過將算法封裝為獨立對象,實現運行時的算法選擇。

  • 動機

    以開發一個文本編輯器為例,其中需要實現自動換行功能。這個功能可能有多種實現方式:簡單換行(按字符數強制分割)、連字符優化換行等

    現實開發中的痛點

    • 擴展性差:混雜各種換行邏輯,核心功能被算法細節淹沒。當新增加新功能時,需要冒著風險修改經過測試的編輯器核心類
    • 靈活性不足:當不同文檔類型需要不同換行策略時,需要復雜的條件判斷
    • 復用性低:當需要為移動端定制換行策略時,不得不創建大量重復代碼
      策略模式如何解決問題?
      通過將換行算法抽象為獨立模塊:
    1. 解耦核心邏輯:文本編輯器只需關注文本處理,不關心具體換行實現
    2. 動態切換策略:根據文檔類型、設備類型或用戶設置,在運行時自由切換算法
    3. 安全擴展:新增算法只需添加新類,無需修改現有代碼(符合開閉原則)
    4. 清晰復用:其他需要換行功能的模塊可以直接調用策略集合
  • 核心概念/角色

    • 📜策略接口(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類圖

implements
implements
implements
_strategy
?interface?
IStrategy
+Execute(int a, int b) : void
AddStrategy
+Execute(int a, int b) : void
SubtractStrategy
+Execute(int a, int b) : void
MultiplyStrategy
+Execute(int a, int b) : void
Context
-_strategy: IStrategy
+Context(IStrategy strategy)
+SetStrategy(IStrategy strategy) : void
+ExecuteStrategy(int a, int b) : void

2.6 UML時序圖

Client Context AddStrategy SubtractStrategy MultiplyStrategy 創建 new AddStrategy() 創建 new SubtractStrategy() 創建 new MultiplyStrategy() 創建 new Context(addStrategy) ExecuteStrategy(10,5) Execute(10,5) 輸出結果 完成 SetStrategy(subtractStrategy) 策略已切換 ExecuteStrategy(10,5) Execute(10,5) 輸出結果 完成 SetStrategy(multiplyStrategy) 策略已切換 ExecuteStrategy(10,5) Execute(10,5) 輸出結果 完成 Client Context AddStrategy SubtractStrategy MultiplyStrategy

三、優缺點

3.1 ?優點

  1. 算法的獨立性:每個算法封裝在獨立的類中,便于維護和單元測試。
  2. 消除條件分支:替代了傳統方式中繁瑣的條件判斷(如if-elseswitch-case結構),每個策略類封裝獨立行為邏輯,使代碼更簡潔、易讀。
  3. 符合開閉原則:新增算法時只需添加新的策略類,無需修改現有的代碼結構,系統對擴展開放、對修改關閉。
  4. 動態切換能力:運行時可靈活切換不同的策略實現,無需重新編譯,系統更靈活。
  5. 代碼清晰與可維護性:通過上下文(Context)與策略解耦,消除算法硬編碼,使業務邏輯更清晰,減少維護成本。

3.2 ? 缺點

  1. 類的數量增加 : 每種策略需要一個獨立的類,可能導致類的數量過多。

  2. 客戶端需要了解所有策略 :客戶端需要知道不同策略的具體實現,以便選擇合適的策略。

  3. 可能導致過度設計 :對于簡單的場景,使用策略模式可能顯得過于復雜。

  4. 維護策略的上下文 :上下文需要維護與策略相關的狀態,可能增加復雜性。

    在策略模式中,上下文需要知道當前使用的是哪個策略,并且在一些情況下,上下文還需要保存一些與策略相關的狀態。

    舉個例子:

    一個訂單處理系統,有不同的折扣策略,比如會員折扣、節日折扣等。這些策略可能會根據訂單金額來計算折扣。

    1. 上下文的角色
      • 上下文類需要知道當前的折扣策略是什么(比如是會員折扣還是節日折扣)。
      • 它還需要保存訂單金額這個狀態,以便在計算折扣時使用。
    2. 復雜性問題
      • 上下文需要管理這些狀態信息,確保在切換策略時狀態是正確的。
      • 如果策略比較多,或者策略之間的狀態依賴關系復雜,上下文的代碼就會變得復雜,增加了理解和維護的難度。

四、最佳實踐場景

4.1 適合場景描述

  • 需要多種算法變體時 🧮
  • 需要運行時切換算法 🔄
  • 需要隔離算法邏輯與業務邏輯📦
  • 消除多個條件判斷語句 🚮

4.2 具體場景

  1. 算法變體密集場景
    • 如加密算法(AES、DES)需根據不同安全級別靈活切換。
    • 支付網關:銀聯、支付寶、微信等支付策略。
  2. 業務規則波動頻繁
    • 促銷活動規則(滿減、折扣、贈品等)
    • 物流配送策略(標準、特快、無人機)
  3. 復雜算法隔離需求
    • 機器學習模型(決策樹、神經網絡、線性回歸)
    • 數據處理流程(批處理、流處理、實時處理)
  4. 多種驗證方式
    • 不同用戶身份驗證(人臉識別、指紋識別、短信驗證碼等)

五、擴展

5.1 繼承復用機制和復合策略

  1. 繼承復用機制:可以通過共同的接口或抽象類提取公共邏輯,減少重復代碼,支持策略族的復用,促進算法演進。
  2. 復合策略擴展:支持組合多個策略應對復雜場景,如組合風險評估和稅務優化策略,增強系統可擴展性。

以下是一個結合繼承復用復合策略擴展的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 模式)

    1. Flyweight 模式的核心:通過一個靜態字典_filters來存儲已經創建的策略對象。這樣可以避免重復創建相同類型的策略對象。
    2. 單例模式的應用:由于_filters是靜態的,FilterFactory類在整個程序中只會維護一份策略對象的映射關系。
    3. 策略對象的復用:當客戶端請求某個類型的濾鏡時,GetFilter方法會先檢查_filters是否已經存在該類型的策略對象。如果不存在,則創建一個新的策略對象并存儲在字典中;如果已經存在,則直接返回已有的對象。
    4. 優化效果:通過這種方式,系統中每種類型的策略對象只會被創建一次,大大減少了對象的實例化數量,節省了內存。
    // 策略工廠類(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];}
    }
    
  • 上下文類

    // 上下文類
    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.

解釋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);}
    }
    
  • 類圖

    聚合
    依賴
    依賴
    依賴
    依賴
    依賴
    ?interface?
    IPaymentParameters
    +GetParameter~T~(string key)
    CreditCardAdapter
    -_cardNumber: string
    -_securityCode: string
    +CreditCardAdapter(string, string)
    +GetParameter~T~(string key)
    AlipayAdapter
    -_phoneNumber: string
    +AlipayAdapter(string)
    +GetParameter~T~(string key)
    ?interface?
    IPaymentStrategy
    +ProcessPayment(double, IPaymentParameters) : void
    CreditCardStrategy
    +ProcessPayment(double, IPaymentParameters) : void
    AlipayStrategy
    +ProcessPayment(double, IPaymentParameters) : void
    PaymentContext
    -_strategy: IPaymentStrategy
    +SetStrategy(IPaymentStrategy) : void
    +ExecutePayment(double, IPaymentParameters) : void
    Program
    +Main() : void
  • 時序圖

    Program PaymentContext CreditCardStrategy CreditCardAdapter AlipayStrategy AlipayAdapter Console 信用卡支付流程 創建實例 創建實例("1234...", "123") SetStrategy(CreditCardStrategy) ExecutePayment(100.0, creditCardParams) ProcessPayment(100.0, creditCardParams) GetParameter<string>("CardNumber") "1234..." GetParameter<string>("SecurityCode") "123" 打印支付信息 支付寶支付流程 創建實例("138...") SetStrategy(AlipayStrategy) ExecutePayment(200.0, alipayParams) ProcessPayment(200.0, alipayParams) GetParameter<string>("PhoneNumber") "138..." 打印支付信息 Program PaymentContext CreditCardStrategy CreditCardAdapter AlipayStrategy AlipayAdapter Console

5.4 客戶端與策略解耦優化

  • 在傳統的策略模式中,客戶端需要了解所有策略類的具體實現細節,并負責選擇合適的策略。然而,為了實現一個靈活的支付策略模式,我們可以通過解耦客戶端代碼與具體策略的方式,使新增支付方式時只需注冊新的策略,而無需修改客戶端代碼。具體實現方式如下:

    1. 引入工廠模式:使用工廠模式來創建策略對象,客戶端只需知道工廠接口,而無需了解具體的策略實現。
    2. 策略注冊與動態選擇:通過注冊機制,允許策略在運行時動態選擇,客戶端只需提供策略標識或條件,而不需了解具體的策略實現。
    3. 使用配置文件:通過配置文件或注解來管理策略的選擇,客戶端可以通過配置而不是硬編碼來選擇策略。
  • 策略接口

    // 策略接口保持不變
    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 映射到 CreditCardPaymentPayPal 映射到 PayPalPayment
    • 還定義了一個默認策略(Default),用于處理未明確指定的支付類型。
    // 配置中心(模擬appsettings.json)
    public static class AppConfig
    {public static readonly Dictionary<string, string> PaymentStrategies = new(){{ "CreditCard", "CreditCardPayment" },{ "PayPal", "PayPalPayment" },{ "Default", "CreditCard" }};
    }
    
  • 初始化注冊

    在程序啟動時,需要將具體的支付策略注冊到工廠中。

    說明:

    • 這里注冊了兩種支付策略:CreditCardPaymentPayPalPayment
    • 這些策略需要實現 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
  • 類圖

    實現
    實現
    查詢策略映射
    獲取策略實例
    聚合存儲策略實例
    OptimizedClient
    +Checkout(string paymentType)
    ?static?
    AppConfig
    +PaymentStrategies: Dictionary<string, string>
    ?static?
    PaymentStrategyFactory
    -_strategies: Dictionary<string, IPaymentStrategy>
    +RegisterStrategy(string key, IPaymentStrategy strategy)
    +GetStrategy(string key) : IPaymentStrategy
    ?interface?
    IPaymentStrategy
    +ProcessPayment(decimal amount)
    CreditCardPayment
    +ProcessPayment(decimal amount)
    PayPalPayment
    +ProcessPayment(decimal amount)
  • 時序圖

    Client OptimizedClient AppConfig PaymentStrategyFactory CreditCardPayment Checkout("CreditCard") PaymentStrategies.TryGetValue("CreditCard") "CreditCardPayment" GetStrategy("CreditCardPayment") CreditCardPayment實例 ProcessPayment(100) 執行支付完成 Client OptimizedClient AppConfig PaymentStrategyFactory CreditCardPayment

六、使用建議

?? 注意事項:

  • 客戶端需理解策略間的功能差異
  • 避免過度設計簡單算法場景
  • 策略接口設計應保持適度抽象
  • 權衡模式引入的架構復雜度
  • 當存在多個相似類僅在行為不同時優先考慮
  • 配合工廠模式使用可以更好地管理策略對象
  • 注意控制策略類的數量膨脹

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/72641.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/72641.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/72641.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

迷你世界腳本顯示板管理接口:DisPlayBoard

顯示板管理接口&#xff1a;DisPlayBoard 迷你世界 更新時間: 2023-04-26 10:21:14 具體函數名及描述如下: 序號 函數名 函數描述 1 showBoard(...) 對玩家顯示顯示板 2 hideBoard(...) 對玩家隱藏顯示板 3 setBoardPicture 對玩家設置顯示板的圖片…

尚硅谷爬蟲note14

一、scrapy scrapy&#xff1a;為爬取網站數據是&#xff0c;提取結構性數據而編寫的應用框架 1. 安裝 pip install scrapy 或者&#xff0c;國內源安裝 pip install scrapy -i https&#xff1a;//pypi.douban.com/simple 2. 報錯 報錯1&#xff09;building ‘twisted.te…

merge函數

merge函數 今天在刷lc&#xff0c;從靈神題解中學到的 來源&#xff1a;560. 和為 K 的子數組 - 力扣&#xff08;LeetCode&#xff09; java8 merge()方法被引入到HashMap類中&#xff0c;用于簡化鍵值對操作&#xff0c;在處理鍵值對時&#xff0c;提供一個重新映射函數來決…

Notepad++ 8.6.7 安裝與配置全攻略(Windows平臺)

一、軟件定位與核心優勢 Notepad 是開源免費的代碼/文本編輯器&#xff0c;支持超過80種編程語言的高亮顯示&#xff0c;相比系統自帶記事本具有以下優勢&#xff1a; 輕量高效&#xff1a;啟動速度比同類軟件快30%插件擴展&#xff1a;支持NppExec、JSON Viewer等200插件跨文…

建筑兔零基礎自學python記錄39|實戰詞云可視化項目——章節分布10(上)

這次我們來制作《紅樓夢》各章節的分布情況&#xff1a; 源代碼&#xff1a; import pandas as pd import numpy as np import matplotlib.pyplot as pltdf_hlm pd.read_csv("hlm.txt", names["hlm_texts"]).dropna()df_hlm df_hlm[~df_hlm.hlm_texts.s…

C++:#ifndef 頭文件保護機制詳解

在C開發中&#xff0c;頭文件可能會被多個源文件包含&#xff0c;導致編譯錯誤。為了避免這種情況&#xff0c;我們使用了頭文件保護機制&#xff08;防止重復包含&#xff09;。 頭文件保護的原理 通過預處理指令#ifndef&#xff08;如果沒有定義&#xff09;和#define&…

利用MQ自動取消未支付超時訂單最佳實踐

一、利用MQ自動取消未支付超時訂單最佳實踐 1、基于 RocketMQ 延遲消息 1.1&#xff1a;延遲消息 當消息寫入到 Broker 后&#xff0c;不會立刻被消費者消費&#xff0c;需要等待指定的時長后才可被消費處理的消息&#xff0c;稱為延時消息。 1.2&#xff1a;實現流程 &am…

基于 ChatGPT 創建專屬 GPTs

文章目錄 基于 ChatGPT 創建專屬 GPTs一、效果展示1.1 中文命名專家1.2 行程小助手 二、核心配置2.1 Instructions2.3 Actions 三、Agent 簡介3.1 功能框架3.2 工作流程3.3 意圖識別 四、數據流程 基于 ChatGPT 創建專屬 GPTs ChatGPT 具備定制 GPTs 的能力&#xff0c;能夠通…

Spring Boot WebFlux 中 WebSocket 生命周期解析

Spring Boot WebFlux 中的 WebSocket 提供了一種高效、異步的方式來處理客戶端與服務器之間的雙向通信。WebSocket 連接的生命周期包括連接建立、消息傳輸、連接關閉以及資源清理等過程。此外&#xff0c;為了確保 WebSocket 連接的穩定性和可靠性&#xff0c;我們可以加入重試…

【數據挖掘】異構圖與同構圖

在圖論&#xff08;Graph Theory&#xff09;中&#xff0c;異構圖&#xff08;Heterogeneous Graph&#xff09;和同構圖&#xff08;Homogeneous Graph&#xff09;是兩種不同的圖結構概念&#xff0c;它們的主要區別在于節點和邊的類型是否單一。 1. 異構圖&#xff08;Hete…

Golang實踐錄:go發布版本信息收集

go發布版本信息收集。 背景 本文從官方、網絡資料收羅有關go的發布歷史概況。主要目的是能快速了解golang不同版本的變更。鑒于官方資料為英文&#xff0c;為方便閱讀&#xff0c;使用工具翻譯成中文&#xff0c;重要特性參考其它資料補充/修改。由于發布版本內容較多&#xf…

【C++】: STL詳解 —— set和map類

目錄 關聯式容器 鍵值對 set set的概念 set的構造函數 set的使用 map map的概念 map的構造函數 map的使用 multiset multimap 關聯式容器 C標準庫提供了多種容器&#xff0c;用于高效管理和操作數據集合。這些容器可分為以下幾類&#xff1a; 順序容器&#xff08;…

DeepSeek:構筑大數據平臺底座的最優解

一、大數據平臺底座的重要性 在數字化浪潮席卷全球的當下,數據已成為企業乃至整個社會最具價值的資產之一 。大數據平臺底座作為數據處理和業務支撐的核心樞紐,其重要性不言而喻,猶如大廈的基石,關乎整個數據生態系統的穩定與發展。 從數據處理角度來看,隨著互聯網、物聯…

Minix OS的配置 SSH C程序編譯

Minix3的下載 官網&#xff1a;https://www.minix3.org/ 安裝 平臺&#xff1a;VMware 開機后進入系統使用setup命令來配置和安裝盡量配置一個DNS服務器&#xff0c;比如8.8.8.8 SSH 安裝&#xff1a;pkgin install openssh 修改配置文件&#xff0c;需要&#xff1a; 修…

ubuntu20 安裝python2

1. 確保啟用了 Universe 倉庫 在某些情況下&#xff0c;python2-minimal 包可能位于 Universe 倉庫中。你可以通過以下命令啟用 Universe 倉庫并更新軟件包列表&#xff1a; bash復制 sudo add-apt-repository universe sudo apt update 然后嘗試安裝&#xff1a; bash復制…

STM32---FreeRTOS中斷管理試驗

一、實驗 實驗目的&#xff1a;學會使用FreeRTOS的中斷管理 創建兩個定時器&#xff0c;一個優先級為4&#xff0c;另一個優先級為6&#xff1b;注意&#xff1a;系統所管理的優先級范圍 &#xff1a;5~15 現象&#xff1a;兩個定時器每1s&#xff0c;打印一段字符串&#x…

docker利用docker-compose-gpu.yml啟動RAGFLOW,文檔解析出錯【親測已解決】

0.問題說明 想要讓RAGFLOW利用GPU資源跑起來&#xff0c;可以選擇docker-compose-gpu.yml啟動。&#xff08;但是官網啟動案例是86平臺的不是NVIDIA GPU的&#xff0c;docker-compose-gpu.yml又是第三方維護&#xff0c;所以稍有問題&#xff09; 1.問題 docker利用docker-c…

【AI深度學習網絡】卷積神經網絡(CNN)入門指南:從生物啟發的原理到現代架構演進

深度神經網絡系列文章 【AI深度學習網絡】卷積神經網絡&#xff08;CNN&#xff09;入門指南&#xff1a;從生物啟發的原理到現代架構演進【AI實踐】基于TensorFlow/Keras的CNN&#xff08;卷積神經網絡&#xff09;簡單實現&#xff1a;手寫數字識別的工程實踐 引言 在當今…

【ThreeJS Basics 06】Camera

文章目錄 Camera 相機PerspectiveCamera 透視相機正交相機用鼠標控制相機大幅度轉動&#xff08;可以看到后面&#xff09; 控制組件FlyControls 飛行組件控制FirstPersonControls 第一人稱控制PointerLockControls 指針鎖定控制OrbitControls 軌道控制TrackballControls 軌跡球…

Linux | Ubuntu 與 Windows 雙系統安裝 / 高頻故障 / UEFI 安全引導禁用

注&#xff1a;本文為 “buntu 與 Windows 雙系統及高頻故障解決” 相關文章合輯。 英文引文&#xff0c;機翻未校。 How to install Ubuntu 20.04 and dual boot alongside Windows 10 如何將 Ubuntu 20.04 和雙啟動與 Windows 10 一起安裝 Dave’s RoboShack Published in…