您的位置:首页 - 教程 - 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


评论: