C++ 之 策略模式

来源:http://www.cnblogs.com/xinxue/archive/2016/06/01/5271184.html
-Advertisement-
Play Games

1 會飛的鴨子 Duck 基類,包含兩個成員函數 (swim, display);派生類 MallardDuck,RedheadDuck 和 RubberDuck,各自重寫繼承自基類的 display 成員函數 現在要求,為鴨子增加會飛的技能 -- fly,那麼應該如何設計呢? 1.1 繼承 考慮到 ...


1  會飛的鴨子 

  Duck 基類,包含兩個成員函數 (swim, display);派生類 MallardDuck,RedheadDuck 和 RubberDuck,各自重寫繼承自基類的 display 成員函數

class Duck {
public:
    void swim();
    virtual void display();
};

class MallardDuck : public Duck {
public:
    void display(); // adding virtual is OK but not necessary
};

class RedheadDuck ...

  現在要求,為鴨子增加會飛的技能 -- fly,那麼應該如何設計呢?

1.1  繼承

  考慮到並非所有的鴨子都會飛,可在 Duck 中加個普通虛函數 fly,則“會飛”的派生類繼承 fly 的一個預設實現,而“不會飛”的派生類重寫 fly 的實現

void Duck::fly() {  std::cout << "I am flying !" << std::endl;  }

void RubberDuck::fly() {  std::cout << "I cannot fly !" << std::endl;  }

1.2  介面

  實際上,使用一般虛函數來實現多態並非良策,在前文 C++11 之 override 關鍵字中的 “1.2 一般虛函數” 已經有所解釋,常用的代替方法是 “純虛函數 + 預設實現”,

即將 fly 在基類中聲明為純虛函數,同時寫一個預設實現

  因為是純虛函數,所以只有“介面”會被繼承,而預設的“實現”卻不會被繼承,是否調用基類里 fly 的預設實現,則取決於派生類里重寫的 fly 函數

void MallardDuck::fly() { Duck::fly(); } 
void RedheadDuck::fly() { Duck::fly(); }

1.3  設計模式

  到目前為止,並沒有使用設計模式,但問題看上去已經被解決了,實際上使用或不使用設計模式,取決於實際需求,也取決於開發者

  <Design Patterns> 中,關於策略模式的適用情景,如下所示:

1) many related classes differ only in their behavior

2) you need different variants of an algorithm

3) an algorithm uses data that clients shouldn't know about

4) a class defines many behaviors, and these appear as multiple conditional statements in its operations

  顯然,鴨子的各個派生類屬於 “related classes”,關鍵就在於“飛”這個行為,如果只是將“飛”的行為,簡單劃分為“會飛”和“不會飛”,則不使用設計模式完全可以

  如果“飛行方式”,隨著派生類的增多,至少會有幾十種;或者視“飛行方式”為一種演算法,以後還會不斷改進;再或“飛行方式”作為封裝演算法,提供給第三方使用。

那麼此時,設計模式的價值就體現出來了 -- 易復用,易擴展,易維護。

  而第 4) 種適用情景,多見於重構之中 -- "Replace Type Code with State/Strategy"

 

2  設計原則

  在引出策略模式之前,先來看面向對象的三個設計原則

1)  隔離變化identify what varies and separate them from what stays the same

   Duck 基類中, 很明顯“飛行方式“是變化的,於是把 fly 擇出來,和剩餘不變的分隔開來

2)  編程到介面program to an interface, not an implementation

  分出 fly 之後,將其封裝為一個介面,裡面實現各種不同的“飛行方式” (一系列”演算法“),添加或修改演算法都在這個介面裡面進行。“介面”對應於 C++ 便是抽象基類,

即將“飛行方式”封裝為 FlyBehavior 類,該類中聲明 fly 成員函數為純虛函數

class FlyBehavior {
public:
    virtual void fly() = 0;
};

class FlyWithWings : public FlyBehavior {
public:
    virtual void fly();
};

class FlyNoWay ...class FlyWithRocket ...

  具體實現各種不同的演算法 -- “飛行方式”,如下所示:

void FlyWithWings::fly() {  std::cout << "I am flying !" << std::endl;  }

void FlyNoWay::fly() {  std::cout << "I cannot fly !" << std::endl;  }

void FlyWithRocket::fly() {  std::cout << "I am flying with a rocket !" << std::endl; }

3)  複合 > 繼承:favor composition (has-a) over inheritance (is-a)

   <Effective C++> 條款 32 中提到,公有繼承即是“is-a”,而條款 38 則提及 Composition (複合或組合) 的一個含義是 “has-a”。因此,可以在 Duck 基類中,

聲明 FlyBehavior 類型的指針,如此,只需通過指針 _pfB 便可調用相應的”演算法“ -- ”飛行方式“

class Duck {
public:
    ...
private:
    FlyBehavior* _pfB;  // 或 std::shared_ptr<FlyBehavior> _pfB;
};

 

3  策略模式

3.1  內容

  即便不懂設計模式,只有嚴格按照上面的三個設計原則,則最後的設計思路也會和策略模式類似,可能只是一些細微處的差別

  下麵來看策略模式的具體內容和結構圖:

  Defines a family of algorithms,  encapsulates each one,  and makes them interchangeable.  Strategy lets the algorithm vary independently

from clients that use it.

 

  Context 指向 Strategy (由指針實現);Context 通過 Strategy 介面,調用一系列演算法;ConcreteStrategy 則實現了一系列具體的演算法

3.2  智能指針

  上例中,策略模式的“介面” 對應於抽象基類 FlyBehavior,“演算法實現”分別對應派生類 FlyWithWings, FlyNoWay, FlyWithRocket,“引用”對應 _pfB 指針

  為了簡化記憶體管理,可以將 _pfB 聲明為一個“智能指針”,同時在 Duck 類的構造函數中,初始化該“智能指針”

Duck::Duck(std::shared_ptr<FlyBehavior> pflyBehavior) : _pfB(pflyBehavior) {}

  直觀上看, Duck 對應於 Context,但 Duck 基類並不直接通過 FlyBehavior 介面來調用各種“飛行方式” -- 即“演算法”,實際是其派生類 MallardDuck,RedheadDuck 和RubberDuck,這樣,就需要在各個派生類的構造函數中,初始化 _pfB

MallardDuck::MallardDuck(std::shared_ptr<FlyBehavior> pflyBehavior) : Duck(pflyBehavior) {}

  然後,在 Duck 基類中,通過指針 _pfB, 實現了對 fly 的調用

void Duck::performFly()
{
    _pfB->fly();
}

  除了在構造函數中初始化 _pfB 外,還可在 Duck 類中,定義一個 setFlyBehavior 成員函數,動態的設置“飛行方式”

void Duck::setFlyBehavior(std::shared_ptr<FlyBehavior> pflyBehavior)
{
    _pfB = pflyBehavior;
}

  最後,main 函數如下:

void main()
{
    shared_ptr<FlyBehavior> pfWings = make_shared<FlyWithWings>();
    shared_ptr<FlyBehavior> pfRocket = make_shared<FlyWithRocket>();

    // fly with wings
    shared_ptr<Duck> pDuck = make_shared<MallardDuck>(pfWings);
    pDuck->performFly();

// fly with a rocket pDuck->setFlyBehavior(pfRocket); pDuck->performFly(); }

 

小結:

1)  面向對象的三個設計原則:隔離變化,編程到介面,複合 > 繼承

2)  策略模式主要涉及的是“一系列演算法“,熟悉其適用的四種情景

 

參考資料:

 <大話設計模式> 第二章

 <Head First Design Patterns> chapter 1

 <Effective C++> item 32, item 38

 <Design Paterns> Strategy

 <Refactoring> chapter 8


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

-Advertisement-
Play Games
更多相關文章
  • N年前我們是這樣來 拼接查詢字元串的: 現在我們使用linq來實現上邊的代碼: 推薦:http://www.cnblogs.com/roucheng/p/dushubiji.html ...
  • ...
  • 聲明:本系列為原創,分享本人現用框架,未經本人同意,禁止轉載!http://yuangang.cnblogs.com 希望大家好好一步一步做,所有的技術和項目,都毫無保留的提供,希望大家能自己跟著做一套,還有,請大家放心,只要大家喜歡,有人需要,絕對不會爛尾,我會堅持寫完~ 如果你感覺文章有幫助,點 ...
  • 用過code first的基本上都不會再想用回mode first或是db first(誰用誰知道)。不要問我為什麼不一開始就直接使用code first,因為那個時候我還不會(甚至還把mode first當成了code first)。 因為工作中使用的就是code first,且越用越習慣,越用... ...
  • 一、使用指針的時候需要註意幾點: 分配空間 初始化 釋放 二、常見的錯誤有幾種: 1)記憶體分配未成功,卻使用了它 編程新手常犯這種錯誤,因為他們沒有意識到記憶體分配會不成功。常用解決辦法是,使用記憶體之前檢查指針是否為Null。 如果指針p是函數的參數,那麼在函數的入口處用assert(p != NUL ...
  • 有很多Web程式中第一次登錄後,在一定時間內(如2個小時)再次訪問同一個Web程式時就無需再次登錄,而是直接進入程式的主界面(僅限於本機)。實現這個功能關鍵就是服務端要識別客戶的身份。而用Cookie是最簡單的身從驗證。 如果用戶第一次登錄,可以將用戶名作為Cookie寫到本地,代碼如下: Cook ...
  • http://www.techug.com/the-difference-of-python2-and-python3#print 這個星期開始學習Python了,因為看的書都是基於Python2.x,而且我安裝的是Python3.1,所以書上寫的地方好多都不適用於Python3.1,特意在Goog ...
  • #include <iostream>#include <string>#include <vector> using namespace std; int main() { int n; while(cin>>n) { vector<string> vec; string input; int i ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...