Sunday, September 9, 2007

Visitor模式

我正在为一个设计问题苦恼。试用期快结束了,我希望自己解决这个问题,来证明自己的

进步。每个人都记得自己的第一份工作吧,也都应该知道在这个时候把活儿做好是多么的

重要!我亲眼看到其他的新雇员没有过完试用期就被炒了鱿鱼,就是因为他们不懂得如何

对付那个大虾...,别误会,我不是说她不好,她是我见过最棒的程序员,可就是有点刻薄

古怪...。现在我拜她为师,不为别的,就是因为我十分希望能达到她那个高度。

我想在一个类层次(class hierarchy)中增加一个新的虚函数,但是这个类层次是由另外一

帮人维护的,其他人碰都不能碰:

class Personnel

{

public:

virtual void Pay ( /*...*/ ) = 0;

virtual void Promote( /*...*/ ) = 0;

virtual void Accept ( PersonnelV& ) = 0;

// ... other functions ...

};

class Officer : public Personnel { /* override virtuals */ };

class Captain : public Officer { /* override virtuals */ };

class First : public Officer { /* override virtuals */ };

我想要一个函数,如果对象是船长(Captain)就这么做,如果是大副(First Officer)就那

么做。Virtual function正是解决之道,在Personnel或者Officer中声明它,而在Captai

nFirst覆盖(override)它。

糟糕的是,我不能增加这么一个虚函数。我知道可以用RTTI给出一个解决方案:

void f( Officer &o )

{

if( dynamic_cast(&o) )

/* do one thing */

else if( dynamic_cast(&o) )

/* do another thing */

}

int main()

{

Captain k;

First s;

f( k );

f( s );

}

但是我知道使用RTTI是公司编码标准所排斥的行为,我对自己说:“是的,虽然我以前不

喜欢RTTI,但是这回我得改变对它的看法了。很显然,除了使用RTTI,别无它法。”

“任何问题都可以通过增加间接层次的方法解决。”

我噌地一下跳起来,那是大虾的声音,她不知道什么时候跑到我背后,“啊哟,您吓了我

一跳...您刚才说什么?”

“任何问...

“是的,我听清楚了,”我也不知道哪来的勇气,居然敢打断她,“我只是不知道您从哪

冒出来的。”其实这话只不过是掩饰我内心的慌张。

“哈,算了吧,小菜鸟,”大虾斜着眼看着我,“你以为我不知道你心里想什么!”她把

声音提高了八度,直盯着我,“那些可怜的C语言门徒才会使用switch语句处理不同的对象

类型。你看:”

/* A not-atypical C program */

void f(struct someStruct *s)

{

switch(s->type) {

case APPLE:

/* do one thing */

break;

case ORANGE:

/* do another thing */

break;

/* ... etc. ... */

}

}

“这些人学习Stroustrup教主的C++语言时,最重要的事情就是学习如何设计好的类层次。

“没错,”我又一次打断她,迫不及待地想让Wendy明白,我还是有两下子的,“他们应该

设计一个Fruit基类,派生出AppleOrange,用virtual function来作具体的事情。

“很好,小菜鸟。C语言门徒通常老习惯改不掉。但是,你应该知道,通过使用virtual f

unction,你增加了一个间接层次。”她放下笔,“你所需要的不就是一个新的虚函数吗?

“是的。可是我没有权力这么干。”

“因为你无权修改类层次,对吧!”

“您终于了解了情况,我们没法动它。也不知道这个该死的类层次是哪个家伙设计的...

我嘀嘀咕咕着。

“是我设计的。”

“啊...,真的?!这个,嘿嘿...”,我极为尴尬。

“这个类层次必须非常稳定,因为有跨平台的问题。但是它的设计允许你增加新的virtua

l function,而不必烦劳RTTI。你可以通过增加一个间接层次的办法解决这个问题。请问

Personnel::Accept是什么?

”嗯,这个...

“这个类实现了一个模式,可惜这个模式的名字起得不太好,是个PNP,叫Visitor模式。

[译者注:PNPPoor-Named Pattern, 没起好名字的模式]

“啊,我刚刚读过Visitor模式。但是那只不过是允许若干对象之间相互迭代访问的模式,

不是吗?”

她叹了一口气,“这是流行的错误理解。那个V,我觉得毋宁说是Visitor,还不如说是Vi

rtual更好。这个PNP最重要的用途是允许在不改变类层次的前提下,向已经存在的类层次

中增加新的虚函数。首先来看看Personnel及其派生类的Accept实现细节。”她拿起笔写下

void Personnel::Accept( PersonnelV& v )

{ v.Visit( *this ); }

void Officer::Accept ( PersonnelV& v )

{ v.Visit( *this ); }

void Captain::Accept ( PersonnelV& v )

{ v.Visit( *this ); }

void First::Accept ( PersonnelV& v )

{ v.Visit( *this ); }

Visitor的基类如下:”

class PersonnelV/*isitor*/

{

public:

virtual void Visit( Personnel& ) = 0;

virtual void Visit( Officer& ) = 0;

virtual void Visit( Captain& ) = 0;

virtual void Visit( First& ) = 0;

};

“啊,我记起来了。当我要利用Personnel类层次的多态性时,我只要调用Personnel::Ac

cept(myVisitorObject)。由于Accept是虚函数,我的myVisitorObject.Visit()会针对正

确的对象类型调用,根据重载法则,编译器会挑选最贴切的那个Visit来调用。这不相当于

增加了一个新的虚拟函数了吗?”

“没错,小菜鸟。只要类层次支持Accept,我们就可以在不改动类层次的情况下增加新的

虚函数了。”

“好了,我现在知道该怎么办了”,我写道:

class DoSomething : public PersonnelV

{

public:

virtual void Visit( Personnel& );

virtual void Visit( Officer& );

virtual void Visit( Captain& );

virtual void Visit( First& );

};

void DoSomething::Visit( Captain& c )

{

if( femaleGuestStarIsPresent )

c.TurnOnCharm();

else

c.StartFight();

}

void DoSomething::Visit( First& f )

{

f.RaiseEyebrowAtCaptainsBehavior();

}

void f( Personnel& p )

{

p.Accept( DoSomething() ); // 相当于 p.DoSomething()

}

int main()

{

Captain k;

First s;

f( k );

f( s );

}

大虾满意地笑了,“也许这个模式换一个名字会更好理解,可惜世事往往不遂人意...”。

No comments: