22. Strategy


Posted by WayneCheng on 2021-01-29

關於 Strategy 本篇將討論以下幾個問題

1. 關於 Strategy

2. UML

3. 將 UML 轉為程式碼

4. 情境


測試環境:

OS:Windows 10
IDE:Visual Studio 2019


1. 關於 Strategy

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

by Gang of Four

  • 定義並封裝一系列演算法,並使它們可切換
  • 策略模式使演算法可依據呼叫端指定而抽換

Strategy(策略)屬於行為型(Behavioral Patterns),當遇到一段程式其中有部分邏輯可由外部指定 時,可藉由 Strategy 將邏輯拆分成獨立 class,之後新增邏輯時只需新增 Strategy 實作即可。

優點:

  • 符合 開閉原則(Open Closed Principle)

缺點:

  • 所有策略實作都需要暴露至呼叫端

2. UML

Class 間關聯:

  • Context 可包含 Strategy
  • ConcreteStrategy A/B/C 繼承 Strategy

Class:

  • Context:使用演算法的物件
  • Strategy:策略的抽象類別或介面
  • ConcreteStrategy:策略實作

3. 將 UML 轉為程式碼

策略介面

/// <summary>
/// 策略介面
/// </summary>
public interface IStrategy
{
    public void AlgorithmInterface();
}

策略實作 A

/// <summary>
/// 策略實作 A
/// </summary>
public class ConcreteStrategyA : IStrategy
{
    public void AlgorithmInterface()
    {
        Console.WriteLine("Called ConcreteStrategyA.AlgorithmInterface()");
    }
}

策略實作 B

/// <summary>
/// 策略實作 B
/// </summary>
public class ConcreteStrategyB : IStrategy
{
    public void AlgorithmInterface()
    {
        Console.WriteLine("Called ConcreteStrategyB.AlgorithmInterface()");
    }
}

策略實作 C

/// <summary>
/// 策略實作 C
/// </summary>
public class ConcreteStrategyC : IStrategy
{
    public void AlgorithmInterface()
    {
        Console.WriteLine("Called ConcreteStrategyC.AlgorithmInterface()");
    }
}

使用演算法的物件

/// <summary>
/// 使用演算法的物件
/// </summary>
public class Context
{
    private IStrategy _strategy;

    public Context(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public void ContextInterface()
    {
        _strategy.AlgorithmInterface();
    }
}
  1. 建立context並指定策略實作 A / B / C
  2. 呼叫策略實作 A / B / C
static void Main(string[] args)
{
    Default.Context context;

    context = new Default.Context(new Default.ConcreteStrategyA());
    context.ContextInterface();

    context = new Default.Context(new Default.ConcreteStrategyB());
    context.ContextInterface();

    context = new Default.Context(new Default.ConcreteStrategyC());
    context.ContextInterface();

    Console.ReadLine();
}

執行結果

Called ConcreteStrategyA.AlgorithmInterface()
Called ConcreteStrategyB.AlgorithmInterface()
Called ConcreteStrategyC.AlgorithmInterface()

4. 情境

我們接到了一個使用者在線上商城可自行選擇折扣方案的需求

  • 同時有多種折扣方案時,使用者可自行選定一種

策略介面

/// <summary>
/// 策略介面
/// </summary>
public interface IStrategy
{
    public int Discount(int amount);
}

策略實作,拆分各種計算結帳金額邏輯

/// <summary>
/// 結帳金額九折
/// </summary>
public class TenPercentOff : IStrategy
{
    public int Discount(int amount)
    {
        Console.WriteLine("結帳金額九折");
        return (int)(amount * 0.9);
    }
}

/// <summary>
/// 滿千折百
/// </summary>
public class ThousandGet100CashBack : IStrategy
{
    public int Discount(int amount)
    {
        Console.WriteLine("滿千折百");
        return amount >= 1000 ? amount - 100 : amount;
    }
}

/// <summary>
/// 送贈品
/// </summary>
public class Giveaway : IStrategy
{
    public int Discount(int amount)
    {
        Console.WriteLine("送贈品");

        return amount;
    }
}

依據使用者選擇折扣計算結帳金額

/// <summary>
/// 依據使用者選擇折扣計算結帳金額
/// </summary>
public class GetPrice
{
    private IStrategy _strategy;

    public GetPrice(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public void Calculate(int amount)
    {
        Console.WriteLine($"原價:{amount}");

        var newAmount = _strategy.Discount(amount);

        Console.WriteLine($"折扣價:{newAmount}");
    }
}
  1. 建立context並選定折扣
  2. 輸入結帳金額計算折扣
static void Main(string[] args)
{
    Situation.GetPrice context;

    context = new Situation.GetPrice(new Situation.TenPercentOff());
    context.Calculate(1600);
    Console.WriteLine("\n");
    context = new Situation.GetPrice(new Situation.ThousandGet100CashBack());
    context.Calculate(1100);
    Console.WriteLine("\n");
    context = new Situation.GetPrice(new Situation.Giveaway());
    context.Calculate(600);

    Console.ReadLine();
}

執行結果

原價:1600
結帳金額九折
折扣價:1440

原價:1100
滿千折百
折扣價:1000

原價:600
送贈品
折扣價:600

完整程式碼

GitHub:Behavioral_09_Strategy


總結

在初期策略數量少且不知道未來會不會增加時使用 Strategy 會提高程式複雜度,建議直接使用 Delegates 來傳入,等到策略數量增加時再使用 Strategy 重構即可。


參考資料

  1. Design Patterns
  2. 大話設計模式
  3. dofactory
  4. Refactoring.Guru

新手上路,若有錯誤還請告知,謝謝


#designpattern #CSharp







Related Posts

Day01 : 數字、字串、變數

Day01 : 數字、字串、變數

Leetcode 刷題 pattern - Top K elements

Leetcode 刷題 pattern - Top K elements

(其他)透過yml設定檔注入參數至特定資料結構

(其他)透過yml設定檔注入參數至特定資料結構


Comments