最近在读“Head First 设计模式”,书中的例子是Java写的,自己用C++实现一遍加深理解。
1、背景环境
某公司做了一套模拟鸭子游戏,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。此系统的内部设计使用了标准的OO技术,设计了一个鸭子基类,并让各种鸭子继承此基类。
class Duck
{
public:
void swim() {cout << "All ducks float, even decoys!" << endl;}
};
class MallardDuck: public Duck
{......};
后来由于创新性需求,需要为鸭子增加一种技能:飞行,该怎么实现呢?
有几种方法可以实现:
1、在基类中添加fly()方法
由于不是所有鸭子都会飞,所以这种方法行不通
2、考虑到不是所有的鸭子都会飞,可在Duck基类中将fly()方法设置为虚函数,则会飞的继承fly(),不会飞的重写fly()
如果不小心忘了重写fly()方法,则会带来错误的结果,所以该方法貌似也行不通
3、在Duck基类中将fly()方法设置为纯虚函数,此时只有基类的接口会被继承,缺省的实现不会被继承
感觉该方法可行
2、策略模式
到目前为止,并没有使用任何设计模式,但问题已经解决了。实际上用不用设计模式,取决于实际需求,也取决于开发者
2.1 适用场景
《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 class”。关键就在于“飞行”这个行为,如果只是将飞行行为简单划分为飞和不会飞,则不用设计模式完全可以。但假如对鸭子的需求不只是能飞和不能飞,还有飞行方式的多样性。则随着派生类的增多飞行方式也会增加至几十种;或者视“飞行方式”为一种算法,以后还会不断改进;再或者将“飞行方式”作为封装算法供第三方使用。那么此时设计模式的价值就体现出来了——易复用,易扩展,易维护。
2.2 设计原则
I、隔离变化
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。Duck基类中变化之处是飞行行为,故把fly行为从Duck类中分离出来。
II、针对接口编程,而不是针对实现编程。
接口对应于C++就是抽象基类,故将fly行为封装到FlyBehavior抽象基类中,并在其不同子类中实现不同的fly方法
class FlyBehavior
{
public:
virtual void fly() = 0;
};
class FlyWithWings: public FlyBehavior
{
public:
virtual void fly()
{
cout << "I'm flying!" << endl;
}
};
class FlyNoWay : public FlyBehavior
{
public:
virtual void fly()
{
cout << "I can't fly" << endl;
}
};
III、委托>继承
公有继承即是is-a;而委托是Delegation即composition by reference(与has-a关系不同),我有接口,但将实现委托给别人做。如你所见,使用委托建立系统具有很大的弹性,不仅可将算法簇封装成类,更可以“在运行时动态地改变行为”。接口不变,但实现可以由不同的实现类(FlyWithWings、FlyNoWay)完成。
在Duck类中,声明FlyBehavior类指针,即:Duck现在拥有接口FlyBehavior类指针,但将实现委托给FlyBehavior的不同实现类完成。
2.3 策略模式定义
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、代码分析
Duck.h:
#ifndef DUCK_H_
#define DUCK_H_
#include "FlyBehavior.h"
#include "QuackBehavior.h"
#include <iostream>
using namespace std;
class Duck
{
public:
Duck(FlyBehavior* ptr_fB);
virtual ~Duck() {}
void swim()
{
cout << "All ducks float, even decoys!" << endl;
}
void performFly();
void setFlyBehavior(FlyBehavior* ptr_fB);
private:
//为行为接口类型声明两个指针变量,所有鸭子子类都继承它们
FlyBehavior* flyBehavior;
};
class MallardDuck: public Duck
{
public:
MallardDuck(FlyBehavior* ptr_fB);
};
#endif
FlyBehavior.h:
FiyWithWings和FlyNoWay都是飞行行为,可以用统一的接口FlyBehavior表示;再写2个实现类FlyWithWings和FlyNoWay,毕竟这是2个不同的飞行行为
#ifndef FLYBEHAVIOR_H_
#define FLYBEHAVIOR_H_
#include <iostream>
using namespace std;
// C++中接口用纯虚基类实现
class FlyBehavior
{
public:
virtual void fly() = 0;
};
class FlyWithWings: public FlyBehavior
{
public:
virtual void fly()
{
cout << "I'm flying!" << endl;
}
};
class FlyNoWay : public FlyBehavior
{
public:
virtual void fly()
{
cout << "I can't fly" << endl;
}
};
#endif
Duck.cpp:
#include <iostream>
#include "Duck.h"
using namespace std;
Duck::Duck(FlyBehavior* ptr_fB): flyBehavior(ptr_fB) {}
void Duck::performFly()
{
// 鸭子对象不亲自处理飞行行为,而是委托给flyBehavior引用的对象
flyBehavior->fly();
}
//动态设定行为
void Duck::setFlyBehavior(FlyBehavior* ptr_fB)
{
flyBehavior = ptr_fB;
}
MallardDuck::MallardDuck(FlyBehavior* ptr_fB): Duck(ptr_fB) {}
MiniDuckSimulator.cpp:
#include <iostream>
#include "Duck.h"
#include "FlyBehavior.h"
using namespace std;
int main()
{
FlyBehavior* ptrFlyWithWings = new FlyWithWings;
FlyBehavior* ptrFlyNoWay = new FlyNoWay;
Duck* mallard = new MallardDuck(ptrFlyNoWay);
// 这会调用MallardDuck继承来的performFly方法,进而委托给该对象的FlyBehavior对象处理(也就是说,调用继承来的flyBehavior引用对象的fly())
mallard->performFly();
cout << "\nDynamic change the flying mode: " << endl;
mallard->setFlyBehavior(ptrFlyWithWings);
mallard->performFly();
//基类中的析构函数必须是虚函数,delete才能调用指针所指对象的对应析构函数
delete mallard;
delete ptrFlyNoWay;
delete ptrFlyWithWings;
system("pause");
}
4、参考资料
Head First 设计模式
Effective C++
https://www.cnblogs.com/xinxue/p/5271184.html