C++ 之 策略模式


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 ...
class RubberDuck ...

  現在要求,為鴨子增加飛的技能 -- 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)

   公有繼承即是 “is-a”,而 Composition (復合或組合) 的含義是 “has-a”,因此,可在 Duck 基類中,聲明 FlyBehavior 型指針,如此,只需通過指針 _pfB 便可調用相應的”算法“ -- ”飛行方式“

class Duck 
{
... private: FlyBehavior* fb_; // 或 std::unique_ptr<FlyBehavior> fb_; };

 

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,“引用”對應 fb_ 指針

  為了簡化內存管理,可將 fb_ 聲明為一個“智能指針”,如此,則不需要手動實現析構函數,采用編譯器默認生成的即可。

Duck::Duck(FlyBehavior *fb)
    : fb_(fb)
{}

3.3  分析 

  直觀上看, Duck 對應於 Context,實際上是其派生類 MallardDuck 等,通過 FlyBehavior 接口來調用各種“飛行方式”。因此,需要在各個派生類的構造函數中,初始化 fb_

MallardDuck::MallardDuck(FlyBehavior *fb)
    : Duck(fb)
{}

 然后,在 Duck 基類中,通過指針 fb_, 實現對 Fly() 的調用

void Duck::PerformFly()
{
    fb_->Fly();
}

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

void Duck::SetFlyBehavior(FlyBehavior *fb)
{
    fb_ = fb;
}

3.4  main 函數  

  因為 main 執行結束后,程序也就結束了,所以對於簡單程序,new 了指針后,可以不用 delete

int main ()
{
    FlyBehavior *pfWings = new FlyWithWings;
    FlyBehavior *pfNo = new FlyNoWay;
    FlyBehavior *pfRocket = new FlyWithRocket;

    // fly with wings
    Duck *pDuck = new MallardDuck(pfWings);
    pDuck->PerformFly();

    // fly with a rocket
    pDuck->SetFlyBehavior(pfRocket);
    pDuck->PerformFly();
}

  

 代碼鏈接: https://github.com/fengyibei/Strategy

 

小結

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

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

 

參考資料

 <大話設計模式> 第二章

 <Head First Design Patterns> chapter 1

 <Effective C++> item 32, item 38

 <Design Patterns> Strategy

 <Refactoring> chapter 8

  Herb Sutter, GotW #91 Solution: Smart Pointer Parameters

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM