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

来源:http://www.cnblogs.com/rossoneri/archive/2016/05/15/5496263.html
-Advertisement-
Play Games

新博客 "wossoneri.com" " 設計模式系列目錄 " 需求情景 比如現在需要做一個收銀軟體,要根據用戶所買商品的單價和數量進行計算。 很簡單,用“單價 數量”即可。 但如果某天需要打折呢? 也很簡單,同一個方法,把折扣作為一個參數,預設值為1,代碼改為“單價 數量 折扣”即可。 恩,看起 ...


新博客wossoneri.com

設計模式系列目錄

需求情景

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

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

但如果某天需要打折呢?

也很簡單,同一個方法,把折扣作為一個參數,預設值為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

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

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

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

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

UML類圖

UML

應用場景和優缺點

應用

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

優點

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

缺點

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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原型模式其實就是從一個對象再創建另外一個可定製的對象,而且不需要知道任何創建的細節。 .NET在System命名空間中提供了ICloneable介面,其中就是唯一的一個方法Clone(),這樣你就只需要實現這個介面就可以完成原型模式。(選至《大話設計模式》) MemberwiseClone()方法, ...
  • 摘要:本文介紹了簡單工廠模式的概念,優缺點,實現方式,以及結合Annotation和反射的改良方案(讓簡單工廠模式不簡單)。同時介紹了簡單工廠模式(未)遵循的OOP原則。最後給出了簡單工廠模式在JDBC中的應用 原創文章。同步自作者個人博客 "http://www.jasongj.com/desig ...
  • "設計模式系列目錄" 新博客 "wossoneri.com" 單一職責原則 Single Responsibility Principle SRP 就一個類而言,應該僅有一個引起它變化的原因。 假設現在要在iPhone上做一個圖片編輯工具。功能有裁剪圖片,旋轉圖片,縮放移動照片等等。 吶,我們可以寫 ...
  • 利用繼承來提供對象的行為,會導致: 1、代碼在多個子類中重覆 2、很難知道所有鴨子的全部行為 3、運行時的行為不易改變 4、改變會牽一發而動全身,造成其他鴨子不想要的改變 設計原則 1 :找出應用之中可能需要變化之處,把他們獨立出來,不要和那些不需要變化的代碼混在一起。 註釋:把會變化的部分取出並“ ...
  • (原文地址:http://www.cnblogs.com/hellohuang/p/5492302.html ) 這是一個簡單實現有分步式框架,由5個服務進程組成一個伺服器,它們分別是世界服(Ws),資料庫處理服(Dp),場景服(Ss),網關服(Fep),框架的思想用來自工作項目框架(但沒有它的代碼 ...
  • 前段時間因為“某度競價排名”的事情鬧得沸沸揚揚的,因為某度搜出來的內容的質量實在是不敢恭維,所以早就戒掉了某度改用Google了。但是Google早就被牆了呢,所以翻牆才能訪問Google呢,這個“翻牆”的過程就是一個代理的過程。“代理模式”在之前的博客中不止一次的提及過,之前的委托回調就是代理模式 ...
  • 一、簡介Dubbo是Alibaba開源的分散式服務框架,它最 大的特點是按照分層的方式來架構,使用這種方式可以使各個層之間解耦合(或者最大限度地 松耦合)。從服務模型的角度來看,Dubbo採用的是一種非常簡單的模型,要麼是提供方提供服務,要麼是消費方消費服務,所以基於這一點可以抽象出服務提 供方(P ...
  • 觀察者模式:在對象之間定義一對多的依賴,這樣一來,當一個對象改變狀態,依賴它的對象都會收到通知,並自動更新。 舉個例子:氣象監測應用 此系統的三個部分是氣象站(獲取實際氣象數據的物理裝置)、WeatherData對象(追蹤來自氣象站的數據,並更新佈告板)和佈告板(有三個佈告板,一個用來顯示目前天氣狀 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...