程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++ 之 策略模式,策略模式

C++ 之 策略模式,策略模式

編輯:C++入門知識

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

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved