复习effective C++ ,今天看到了"virtual 函数以外的其它选择",里面介绍了Strategy 模式的两种实现方式,也介绍了std::function 和 std::bind 函数等,结合这段时间学习的进行一个总结。
首先还是先来回顾书上的内容:
问题引入:
一个游戏需要对其中人们生命值,健康状况进行监控计算,因此需要定义一个专门的函数,但是不同的人物的计算方式是不同的,也就是说这个函数需要不同的实现方式。可以使用多态,这是最基本的方法。
实例:
class GameCharacter {
public:
virtual int healthValue() const;// return character's health rating
... // derived classes may redefine this
};
由于healthValue()没有声明为pure virtual , 这暗示我们将有个计算健康指数的缺省算法。这也可能成为一个弱点,在派生类中考虑重定义该函数的时候考虑不周。
因此,也可以使用“函数指针”作为成员变量,对于不同人物传给这个“函数指针”不同的计算函数。这就是:使用 Function Pointer实现Strategy模式。
class GameCharacter; // forward declaration // function for the default health calculation algorithm int defaultHealthCalc(const GameCharacter& gc); class GameCharacter { public: typedef int (*HealthCalcFunc)(const GameCharacter&); explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf){} int healthValue() const { return healthFunc(*this); } ... private: HealthCalcFunc healthFunc; }; //下面是同一类人中的不同实现: class EvilBadGuy: public GameCharacter { public: explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) : GameCharacter(hcf) { ... } ... }; int loseHealthQuickly(const GameCharacter&);// health calculation int loseHealthSlowly(const GameCharacter&);// funcs with different behavior EvilBadGuy ebg1(loseHealthQuickly); //sametype charac-ters with EvilBadGuy ebg2(loseHealthSlowly); //different health-related behavior
从上面的代码可以看出,这样的代码具有很好的扩展性,虽然都是EvilBadGuy对象,但是通过绑定函数指针的方式可以使得实现方式灵活多变。
上面的:
typedef int (*HealthCalcFunc)(const GameCharacter&);
作用是定义 一个 返回值为 int 类型,以GameCharacter 的引用为参数的的函数的类型,以后用HealthCalcFunc 声明的函数都是这个类型。(记得以前竟然看不懂这种方式)。
也可以使用 tr1::function 来完成 Strategy 模式(注:tr1::fucntion 现在已经是C++11 标准的一部分,存在于std namespace 中)
class GameCharacter; // as before int defaultHealthCalc(const GameCharacter& gc); // as before class GameCharacter { public: // HealthCalcFunc is any callable entity that can be called with // anything compatible with a GameCharacter and that returns // anything compatible with an int; see below for details typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc; explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf){} int healthValue() const { return healthFunc(*this); } ... private: HealthCalcFunc healthFunc; };
实现了上面的类之后,计算“健康状况”可以用下面的方式都行:
short calcHealth(const GameCharacter &);
struct HealthCalculator { // class for health int operator()(const GameCharacter&) const // calculation function { ... } // objects };
class GameLevel { public: float health(const GameCharacter&) const; // health calculation ... // mem function; note }; // non-int return type class EvilBadGuy: public GameCharacter { // as before ... };
class EyeCandyCharacter: public GameCharacter { // another character ... // type; assume same };
// constructor as EvilBadGuy EvilBadGuy ebg1(calcHealth); // character using a // health calculation function EyeCandyCharacter ecc1(HealthCalculator()); // character using a // health calculation // function object GameLevel currentLevel; ... EvilBadGuy ebg2( // character using a std::tr1::bind(&GameLevel::health, // health calculation currentLevel, // member function; _1) // see below for details );
注:上面的bind函数,有两个参数第一个是currentLevel 实际上是this指针,因为我们知道类的成员函数其实都有一个隐藏的参数 this 指针,在使用bind函数的时候需要指明该参数;后面的_1 叫做占位符,我们实际传进来的参数只有一个就用这个占位符代替。
下面来看一下Strategy 模式的经典实现方法:
这个图告诉你:GameCharacter 是某个继承体系的根类,体系中的EvilBadGuy和EyeCandyCharacter 都是derived classes; HealthCalcFunc 是另一个继承体系的根类,体系中的SlowHealthLoser 和 FastHealthLoser 都是derived class,每个GameCharacter 对象内含一个指针,指向一个来自HealthCalcFunc继承体系的对象。
class GameCharacter; // forward declaration class HealthCalcFunc { public: ... virtual int calc(const GameCharacter& gc) const { ... } ... }; HealthCalcFunc defaultHealthCalc; class GameCharacter { public: explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc) : pHealthCalc(phcf){} int healthValue() const { return pHealthCalc->calc(*this);} ... private: HealthCalcFunc *pHealthCalc; };
当你为解决问题而寻找某个设计方法时,不放考虑virtual函数的替代方案。一下有几个替代方案:
- 使用non-virtual interface (NVI)手法,它以public non-virtual成员函数包裹较低访问性(private或protected)的virtual函数
- 将virtual函数替换为“函数指针成员变量”,这是Strategy设计模式的一部分表现形式
- 以“tr1 :: function”成员变量替换virtual函数,因而允许使用任何可调用实体搭配一个兼容于需求的签名式。
- 将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现手法。
附:UML关系图的含义:
实际上,有两种不同的 has-a 关系。一个对象可以拥有另一个对象,其中被包含的对象是包含对象的一部分——或者不是。下图 中,我表示出 Airport拥 Aircraft。Aircraft 并不是 Airport 的一部分,但仍然可以说 Airport 拥有 Aircraft,这种关系称为聚集。
另一种 has-a 关系是包含,被包含对象是包含对象的一部分,这种关系也称为组合。
下图显示了 Car(轿车)拥有 Tire(轮胎),后者是它的一部分(也就是说,Car 由 Tire 和其他东西组成),这种 has-a 关系,称为组合关系(composition),用实心菱形表示。此图上还显示了 Car 使用了 GasStation(加油站)类,这种使用关系用带箭头的虚线表示,也称依赖关系(dependencyrelationship)。
组合和聚集都有“一个对象包含一个或多个对象”的意思,但是,组合意味着“被包含对象是包含对象的一部分”,而聚集意味着被包含对象更像是一个集合。我们可以认为组合是一种非共享的关联,被包含对象的生存周期由包含对象控制。适当使用构造函数和析构函数在这里有助于对象的创建和销毁过程。