您的位置:首页 > 编程语言 > C语言/C++

细读《Effective C++》之八

2007-11-10 22:51 169 查看
Chapter 6. Inheritance and Object-Oriented Design
Item 35


条款35:Consider alternatives to virtual functions

当我辛辛苦苦用virtual functions提供了接口和缺省实现之后,Scott却提出考虑其它解法……

需求是什么来着?

GoF认为类行为模式Template Method模式的意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

这 和以前使用virtual functions的最大区别在于,我们在derived classes中重载virtual functions来实现dereived classes所需要的功能即算法。而Template Method模式不去改变算法的框架和步骤,仅仅是在derived class中将特定的步骤重载。

说到底,二者最终都是通过 virtual functions实现算法,这有悖于GoF的第二条原则:优先使用对象组合,而不是类继承。需要注意的另外一个问题是:Template模式获得一种反 向控制结构效果,这也是面向对象系统的分析和设计中一个原则DIP(依赖倒置:Dependency Inversion Principles)。其含义就是父类调用子类的操作(高层模块调用低层模块的操作),低层模块实现高层模块声明的接口。这样控制权在父类(高层模 块),低层模块反而要依赖高层模块。

继承的强制性约束关系也让Template模式有不足的地方,我们可以看到对于DeriveClass 类中的实现的原语方法Primitive1(),是不能被别的类复用。假设我们要创建一个BaseClass的变体 AnotherAbstractClass,并且两者只是通用算法不一样,其原语操作想复用BaseClass的子类的实现。但是这是不可能实现的,因为 DeriveClass继承自BaseClass,也就继承了BaseClass的通用算法,AnotherAbstractClass是复用不了 DeriveClass的实现,因为后者不是继承自前者。

这儿引出了另一种避开继承的对象行为模式:Strategy模式,其意图是定义一 系列的算法,把它们一个个封装起来,并且使它们可相互替换,本模式使得算法可独立于使用它的客户而变化。每一个算法被称为一个strategy。 Scott给出了三种Strategy模式的实现:

这儿是两种使用virtual functions & inheritance的对比实现:

1) virtual functions:




class CBase ...{


public:


virtual void Algorithm();


...


};






class CDerived : public CBase ...{


public:


virtual void Algorithm();


...


};






void CDerived::Algorithm()...{


...


}

2) Template Method Pattern:


// having clients call private virtual functions indirectly


// through public non-virtual member functions


// is known as the non-virtual interface (NVI) idiom




class CBase ...{


public:


void Algorithm(); // wrapper


...





private:


virtual void DoAlgorithm();


...


};






void CBase::Algorithm()...{


// do "before" stuff : locking a mutex, making a log entry, verifying


// that class invariants and function preconditions are satisfied, etc


Primitive1();





DoAlgorithm();





// do "after" stuff : unlocking a mutex, verifying


// function postconditions, reverifying class invariants, etc


Primitive2();


}






class CDerived : public CBase ...{


...


private:


virtual void DoAlgorithm();


...


};






void CDerived::DoAlgorithm()...{


...


}

以下是三种Strategy模式的对比实现:

1) The Strategy Pattern via Function Pointers:

a) Different instances of the same character type can have different health calculation functions.

b) Health calculation functions for a particular character may be changed at runtime. For example, GameCharacter might offer a member function, setHealthCalculator, that allowed replacement of the current health calculation function.


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




// 如果不传递任何函数名称作为实参,将以defaultHealthCalc构造EvilBadGuy对象


EvilBadGuy ebg1(loseHealthQuickly); // same-type characters


EvilBadGuy ebg2(loseHealthSlowly); // with different health-related behavior

2) The Strategy Pattern via tr1::function:


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&); // health calculation function;


// note non-int return type






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


);

3) The "Classic" Strategy Pattern:

GoF的Strategy模式将接口类(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;


};

由于对BOOST和TR1知识的匮乏,除了用TR1实现的Strategy我无法更加深入地体会之外,其它理解和接受起来都并不困难。

Things to Remember

1) Alternatives to virtual functions include the NVI idiom and various forms of the Strategy design pattern. The NVI idiom is itself an example of the Template Method design pattern.

2) A disadvantage of moving functionality from a member function to a function outside the class is that the non-member function lacks access to the class's non-public members.

3) tr1::function objects act like generalized function pointers. Such objects support all callable entities compatible with a given target signature.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: