文章来源:http://www.gotw.ca
版权归属:Herb Sutter and Jim Hyslop
译 者:morningsing(CSDN)
悬在几乎一百公里以外的天空,那个小小的行星占据了观察孔视野相当大的部分。它的表面看起来好像玻璃碎裂后又用冰冻的胶水修补过。许多冻结的河流蜿蜒在十字形的低矮山脉之间。荒凉的木卫二是那样的美轮美奂。
扬声器传出“上船,第三和第七区”,然后又恢复沉默。
珍妮和我在观察口边上和一群专家挤作一团,我们中的大多数看起来像是瞠目结舌的年轻宇航员。可我们不在乎。当然,在飞船中的任何地方都可以看到更好的景色,但当看到真实、原原本本的光直接从月球反射进你的眼睛时,总能有一丝特殊的感觉。
“真是个好地方,” 珍妮自告奋勇的说。“他们将消息封锁得紧紧的,现在也还这样。”
“紧紧的?他们应该告诉我们真正的目的地。”
“不会。我相信我们会很快找出他们在冰层底下发现了什么。”
“嗯?嗯。”我回答,“仅仅来到这里,我就很高兴。当我以前看到这里的图片,还以为是处理过的幻境,没想到真的这么美——不,比图片更美。我们真的来到这里了。这使我想起自己的第一份工作。”
她开玩笑地哼了一声。“任何东西都会使你想起第一份工作。”
“我还年轻,所以容易受到感染。无论如何,刚才关于这次旅行的谈论又使我想起了它。”
“啊。”她停顿了一下。“你到底想说什么?”
我笑了,接下去解释……
- - - - - - - - - - - - - - - - - - - - - - - - -
我曾经为一个设计问题苦苦挣扎。我真的希望自己解决,因为见习期即将结束,我希望证明自己。
你记得毕业后的第一份工作,对吧?正确地处理事情是多么的重要。我一直记得新进公司的同事们,因为无法与Guru打好交道而没有通过见习。别误解我的意思,她是一个优秀的程序员,只是有一点儿……古怪。现在她是我的良师益友,我非常希望在她面前证明自己。
我需要在类体系中加入一个新的虚函数,但其他团队维护着类体系并且不允许别人改动。这个类体系看起来像是这样:
class Personnel { public: virtual void Pay ( /*...*/ ) = 0; virtual void Promote( /*...*/ ) = 0; virtual void Accept ( PersonnelV& ) = 0; // … 其他函数 … }; class Officer : public Personnel {{ /* 改写虚拟函数 */ }; class Captain : public Officer { /* 改写虚拟函数 */ }; class First : public Officer { /* 改写虚拟函数 */ };
我需要一个函数在对象是Captain和First军官时拥有不同的行为。虚拟函数恰好合适,在Personnel或Officer中声明,在Captain和First中改写。
麻烦是:我不能加入新的函数。我知道仅仅加入使用运行时类型信息的类型检查就可以解决问题,像这样:
void f( Officer &o ) { if( dynamic_cast<Captain*>(&o) ) /* do one thing *//* 某些操作 */ else if( dynamic_cast<First*>(&o) ) /* do another thing *//* 某些操作 */ } int main() { Captain k; First s; f( k ); f( s ); }
但我知道这种类型检查是不符合公司的编程规范的。“我不喜欢这样,”我对自己,也对别人这样说:“但这一次,我觉得有充足的理由让他们更改这个规范。很显然,没别的方法可以解决问题啊。”
“所有的问题都可以通过多加一个间接层来解决。”
我吓了一跳,Guru在我身后说话呢!“什么?”我转向她问。
“所有的问——”
“哦,是的。”我禁不住打断她。“我听见了你所说的,我只是不知道您从哪儿来的。”老天!我想这是一种掩饰,不过部门里的大半同事都会这么做的。
“啊,不过你会的。”Guru把身体探向我说:“你会知道的,我年轻的徒弟。”她专注地盯了我一会儿,然后直起身子。“我的孩子,C的信徒经常使用基于对象类型的分支语句来引导程序流。考虑这个比喻。”她拿起一支干擦笔:
/* 一个典型的C程序 */
void f(struct someStruct *s) { switch(s->type) { case APPLE: /* 进行某些操作 */ break; case ORANGE: /* 进行某些其他操作 */ break; /* 等等 */ } }
“当这些信徒向先知Stroustrup学习时,”她继续说,“也就是说学习C++时,他们必须学习如何设计好的类体系。”
“是的,”我再次打断,渴望表现出自己确实懂一些东西,“他们应该创建一个以Fruit为基类的类体系,然后派生Apple和Orange。这不错吧?派生类中应该使用虚拟函数。”
“很好,我的孩子。C++的信徒经常借用这个比喻指出switch语句烦琐,容易滋生错误,劝说C的执迷者改投C++的怀抱。。然而从另一种角度看待这个比喻:通过引进一个虚拟函数,你就加入了一个间接层。”她放下笔,“你所需要的是一个新的虚拟函数。”
“啊哈,很对。我知道,”我炫耀地总结道:“而且我现在无法这样做。”
她点点头。“因为你不能修改类体系。是的。”
这使我迟疑了一下,但仅仅是一瞬间。“哦,你知道我们无法改变它?那么你明白了。”
“哦,是的。是我设计了类体系。”
“哦。”这使我彻底住了嘴。
“因为非同寻常的系统间交叉依赖,这个体系较之一般情况必须更加稳定,但允许你加入虚函数来避免类型分支(type-switching)代码。你可以通过加入新的间接层来解决问题。再次考虑那个比喻。 Personnel:: Accept是做什么的?”
“呃,嗯?”我如坠雾中。
“这个类实施的是Poorly Named模式,或者说PNP。也被称作Visitor模式[1]。”
“呃,嗯。”我现在明白了一些。“我曾经读到过Visitor模式。但那只是针对能够互相迭代访问的对象,不是么?”
她叹了口气。“一个常见的误解。考虑一下V的含义,不是Visitor而是Virtual。”她解释道,“PNP最有用的地方,就是在不会进一步改变类体系的情况下增加虚拟函数。首先注意Accept在Personnel类和子类中是如何实现的。”她摘出如下代码:
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::Accept(myVisitorObject)。因为Accept是虚函数,myVisitorObject.Visit会以正确派生类型调用,而且重载解析过程会选出正确地函数。这就像加入了一个新的虚函数,是么?”
“的确是的,我的孩子。新加入的函数通常只需占用客户类的公共接口,但在其他任何方面都像虚函数一样,即使它并非成员函数。所需的一切只是初始类体系支持Accept,以使自己能够扩展。如果将来我们发现经常用新的子类扩展Personnel体系,或者希望更好地管理编译依赖,可以移植到Acyclic Visitor——除了稍许修改Accept函数,完全不必改变Personnel及其基类。[2, 3]
我明白了。“OK,那我可以这样做。”我飞快的写道:
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(); }
Guru专心地盯着我的代码。“这些代码是为什么系统写的?”
“啊……是个模拟器。”
“我知道了。好的,继续吧,孩子。写剩下的吧。”
我增加了一个测试例程:
void f( Personnel& p ) { p.Accept( DoSomething() ); // like 类似 p.DoSomething() } int main() { Captain k; First s; f( k ); f( s ); }
Guru踱步离去,一边压了压耳后一络灰白的头发:“很好。确实,这个函数没有直接将原先的类体系局限于personnel::作用域。但在这时,这个函数不管使用任何名字都是虚拟的。”她笑着走开。她的声音随着身影慢慢消失。“Visitor模式即使采用了别的名字也一样有用,而且更容易描述。不过事情总是这样的……”
- - - - - - - - - - - - - - - - - - - - - - - - -
随着飞船的旋转,巨大的木星——它的壮美使木卫二表面相形见绌——渐渐进入了观察孔的视野。
“我将申请使用一会儿着陆服,”我决定:“能在这样的天空下走走该多好啊。即使我们只是旅行者。”
“我们是旅行者。”珍妮赞成道:“不过我想知道我们是不是第一批到达的?”
于是,一段时间里,我们只是默默的望着天空。 [注释]
[1] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995).
[2] R.C. Martin. "Design Patterns for Dealing with Dual Inheritance Hierarchies," C++ Report, April 1997). Available online at http://www.objectmentor.com/publications/dih.pdf.
[3] R.C. Martin, "Acyclic Visitor," in R.C. Martin, D. Riehle, F. Buschmann (eds.), Pattern Languages of Program Design 3 (Addison-Wesley, 1998). Available online at http://www.objectmentor.com/publications/acv.pdf.
[关于作者]Herb Sutter
是个独立顾问,也是ISO/ANSI C++标准委员会的秘书。你可通过[email protected].联系他
Jim Hyslop
Leitch Technology International Inc.资深的软件设计师,你可通过[email protected]联系他
本文地址:http://com.8s8s.com/it/it2462.htm