[設計模式]之二:策略模式

vsng2487 8年前發布 | 21K 次閱讀 設計模式

需求情景

比如現在需要做一個收銀軟件,要根據用戶所買商品的單價和數量進行計算。

很簡單,用“單價 * 數量”即可。

但如果某天需要打折呢?

也很簡單,同一個方法,把折扣作為一個參數,默認值為1,代碼改為“單價 數量 折扣”即可。

恩,看起來都很美好。現在又要加需求,我要滿300減100,我還要滿200送50…

OK,現在就得回到面向對象上來了。向上次簡單工廠一樣,把所有計算價格可能的方法封裝成一個個類。比如一個打一折類,一個打兩折類…唉等等,這可不對。上次加、減、乘、除分別封裝是因為他們屬于同一種類型,但是有不同的實現方法。而這次,對于打折來說,不論打幾折,打折的計算方式都是一樣的,只是形式不同,但本質是一樣的。同理,滿減和返利也是兩種類型,但各自有多種實現。

面向對象的編程,并不是類越多越好,類的劃分是為了封裝,但分類的基礎是抽象,具有相同屬性和功能的對象的抽象集合才是類

所以可以開始編碼,先抽象一個計算收款的類,抽象一個收錢的方法,然后根據不同打折類型實現不同的收錢方法。

  • @interface Cash : NSObject

    • (CGFloat)acceptOriginCash: (CGFloat)money;

    @end @implementation Cash

    • (CGFloat)acceptOriginCash: (CGFloat)money { return money; }

    @end ///正常價錢 @implementation CashNormal

    • (CGFloat)acceptOriginCash:(CGFloat)money { return money; }

    @end ///折扣 @interface CashRebate : Cash

    @property (nonatomic, assign) CGFloat rebate;

    @end @implementation CashRebate

    • (instancetype)init { self = [super init]; if (self) {

        _rebate = 1.0; //默認不打折
      

      } return self; }

    • (CGFloat)acceptOriginCash:(CGFloat)money { return money * _rebate; }

    @end ///滿返 @interface CashReturn : Cash

    @property (nonatomic, assign) CGFloat moneyCondition; @property (nonatomic, assign) CGFloat moneyReturn;

    @end @implementation CashReturn

    • (instancetype)init { self = [super init]; if (self) {

        _moneyReturn = 0;
        _moneyCondition = 0;
      

      } return self; }

    • (CGFloat)acceptOriginCash:(CGFloat)money {

      if (_moneyCondition == 0 || _moneyReturn == 0 || money < _moneyCondition) {

        return money; //沒有返現
      

      } else {

        int returnCount = floorf(money / _moneyCondition);
        money -= returnCount * _moneyReturn;
        return money;
      

      }

    } @end </code></pre> </td> </tr> </tbody> </table>

    創建好以上幾種收費類型,設想一下,一般打折時都會列出相應的打折商品,也就是說平時不是所有的商品都打折,這時候假設我們專門寫好一個折扣日的類,類中包含了打折商品列表,當然也包含了打折的方式等其他信息。繼續用面向對象的思想去思考,折扣日應該也分好幾種,比如周末,五一,工作日等等,所以折扣日也可以抽象一個基類出來,這個基類就應該包含返回折扣結果的抽象方法

    OK,到這里問題就來了,不同的折扣日都有相同的獲取最終價錢的方法,而對于價錢的計算策略卻完全不同,也就是每個具體的折扣日實現這個返回折扣結果的抽象方法都不一樣。那該怎么做?

    設計原則:找到系統中變化的部分,將變化的部分同其它穩定的部分隔開。換句話說就是:”找到變化并且把它封裝起來,稍后你就可以在不影響其它部分的情況下修改或擴展被封裝的變化部分。”盡管這個概念很簡單,但是它幾乎是所有設計模式的基礎,所有模式都提供了使系統里變化的部分獨立于其它部分的方法。

    可以看出,每個折扣日都要實現基類返回折扣結果的方法,但實現的方法不一樣。而計算方法都是經過了封裝的,保證計算方法不被改變,也保證改變一個不會影響到其他計算方法。在這種情況下,就可以考慮使用策略模式。

    策略模式

    策略模式定義了算法家族,分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化不會影響到使用算法的客戶。

    以上幾種收錢方式都是一些算法,算法本身只是一種策略,最重要的是這些算法是隨時都可能且可以互相替換的,這就是變化點,而封裝變化點是我們面向對象很重要的思維方式。

    所以這里的思路是創建一個上下文類,用策略對象作為構造參數,來維護一個對策略對象的引用。同時這樣也不會受到拓展的影響。

    @interface CashContext : NSObject

    • (instancetype)initWithCash: (Cash *)cash;
    • (CGFloat)getResult: (CGFloat)money;

    @end /// @interface CashContext() { Cash *_cash; } @end

    @implementation CashContext

    • (instancetype)initWithCash: (Cash *)cash { self = [super init]; if (self) {

        _cash = cash;
      

      } return self; }

    • (CGFloat)getResult: (CGFloat)money { return [_cash acceptOriginCash:money]; }

    @end </code></pre> </td> </tr> </tbody> </table>

    創建好Context類,就可以通過構造方法選擇不同的策略來實現計算:

    CashContext *context = [[CashContext alloc] initWithCash:[[CashRebate alloc] initWithRebate:0.8]];//打8折
    CGFloat value = [context getResult:400]]//原價400
    

     

    UML類圖

    UML

    應用場景和優缺點

    應用

    • 多個類只區別在表現行為不同,可以使用Strategy模式,在運行時動態選擇具體要執行的行為。
    • 需要在不同情況下使用不同的策略(算法),或者策略還可能在未來用其它方式來實現。
    • 對客戶隱藏具體策略(算法)的實現細節,彼此完全獨立。(你只要知道Context類的接口,不必知道折扣算法內部是怎么實現的)

    實際上,一些平時常見的方法就是用的策略模式,比如說Swift(很多其他語言也是)里的數組的Sort函數

    func biggerNumberFirst( a: Int, _ b: Int ) -> Bool {
        return a > b
    }
    arr.sort(biggerNumberFirst)
    

     

    Sort函數可以添加參數,上面代碼中是我們自己定制一個策略,然后作為參數傳給Sort去處理。當然也可以定制其他策略或者使用系統默認的一些策略去進行排序工作。這就是策略模式一個很典型的應用。

    優點

    • 提供了一種替代繼承的方法,而且既保持了繼承的優點(代碼重用)還比繼承更靈活(算法獨立,可以任意擴展)。
    • 避免程序中使用多重條件轉移語句,使系統更靈活,并易于擴展。
    • 遵守大部分GRASP原則和常用設計原則,高內聚、低偶合。

    缺點

    • 因為每個具體策略類都會產生一個新類,所以會增加系統需要維護的類的數量。

    參考
    鴨子-策略模式(Strategy)
    這篇文章更深入形象,推薦閱讀

     本文由用戶 vsng2487 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
  • sesese色