[Design Pattern] 策略模式 Strategy Pattern

策略模式 => 我希望用戶只認識我 連實際他的需求是哪個子類別在做都不要知道

 

考量前一篇 簡單工廠時 用戶端叫用的程式碼為

static void Main(string[] args)
{
    Housework housework = HomeworkFactory.CreateHousework("掃地");
    housework.Do();
}

 

 

等於用戶端得同時認識

1. 當下實際得到哪個 Housework 的子類別 ex: 可能是掃地、洗碗、倒垃圾

2. 還得知道 HomeworkFactory (畢竟要用哪個子類別是他幫我們分派的)

3. 父類別.Do()

 

而假設我們今天的需求為 只要有 Do() 就好

實際是哪個子類別在Do() 主程式不用知道 => 我想把子類別在主程式中隱藏起來

因為在主程式中的程式碼越單純 未來程式碼有變動時 要改的地方就越少 越集中

一樣先來看看最重要的 Context 的程式碼

/// <summary>
/// 實際用到哪個子類別完全在 Context 中完成
/// 命名 => xxxContext
/// </summary>
class DiscountContext
{
    //重點!!! 父類別相依於 Context 而不是主程式 不會再曝露任何資訊給外人
    private Discount _disc;

    //工廠模式傳回一個類別 策略模式不用傳回任何東西
    //因為實際用哪個類別外人不會知道 不用跟外人說
    //都是在內部 (建構子) 完成的
    public DiscountContext(string type)
    {
        switch (type)
        {
            case "買一送一":
                _disc = new Buy1Get1Free();
                break;
            case "半價":
                _disc = new HalfPrice();
                break;
            case "無折扣":
                _disc = new NoDiscount();
                break;
        }
    }

    //父類別的方法又被我包一層 因為對外我希望大家都透過 Context 存取所有東西 不需再跟其他人有關聯
    public int GetPrice(int orgPrice)
    {
        return _disc.Calculate(orgPrice);
    }
}

 

可得知為了達成一切都透過 Context 完成就好 不要再跟其他人有瓜葛 我們得做兩件事

1. Context 內有一個私有的父類別變數 實際是對應到哪個子類別 在建構子中決定

2. 父類別的 func 在 Context 中再包一層 所以主程式 call 的是 Context 的 Func 而非主程式的 Func

最後達成 => 斷開主程式與類別之間的藕合

 

所以主程式會長得像

/// <summary>
/// 主程式完全不知道實際是用哪個子類別
/// </summary>
static void Main(string[] args)
{
    string strategy = "買一送一";
    DiscountContext context = new DiscountContext(strategy);

    //簡單工廠模式叫用的 func 在父類別中
    //策略模式叫用的 func 就在 Context 中
    //所以策略模式的主程式只需要用到 Context 不用知道父類別是誰
    //未來如果程式有變動 主程式要改的地方就更少了
    var price = context.GetPrice(100);
}

 

那最後我們就能看看完整的程式碼長怎樣了


namespace ConsoleApplication1
{
    class Program
    {
        /// <summary>
        /// 主程式完全不知道實際是用哪個子類別
        /// </summary>
        static void Main(string[] args)
        {
            string strategy = "買一送一";
            DiscountContext context = new DiscountContext(strategy);

            //簡單工廠模式叫用的 func 在父類別中
            //策略模式叫用的 func 就在 Context 中
            //所以策略模式的主程式只需要用到 Context 不用知道父類別是誰
            //未來如果程式有變動 主程式要改的地方就更少了
            var price = context.GetPrice(100);
        }
    }

    //-----------------------------------------------------------------------------------------------
    /// <summary>
    /// 實際用到哪個子類別完全在 Context 中完成 不會再曝露任何資訊給外人
    /// 命名 => xxxContext
    /// </summary>
    class DiscountContext
    {
        //重點!!! 父類別相依於 Context 而不是主程式
        private Discount _disc;

        //工廠模式傳回一個類別 策略模式不用傳回任何東西
        //因為實際用哪個類別外人不會知道 不用跟外人說
        //都是在內部 (建構子) 完成的
        public DiscountContext(string type)
        {
            switch (type)
            {
                case "買一送一":
                    _disc = new Buy1Get1Free();
                    break;
                case "半價":
                    _disc = new HalfPrice();
                    break;
                case "無折扣":
                    _disc = new NoDiscount();
                    break;
            }
        }

        //父類別的方法又被我包一層 因為對外我希望大家都透過 Context 存取所有東西 不需再跟其他人有關聯
        public int GetPrice(int orgPrice)
        {
            return _disc.Calculate(orgPrice);
        }
    }

    //-----------------------------------------------------------------------------------------------
    abstract class Discount
    {
        public abstract int Calculate(int orgPrice);
    }

    class Buy1Get1Free : Discount
    {
        public override int Calculate(int orgPrice)
        {
            //買一送一 以價高者計費
            return -1;
        }
    }

    class HalfPrice : Discount
    {
        public override int Calculate(int orgPrice)
        {
            //售價 / 2
            return -1;
        }
    }

    class NoDiscount : Discount
    {
        public override int Calculate(int orgPrice)
        {
            //原價
            return -1;
        }
    }
}