C_and_CPP 版 (精华区)

发信人: seaboy (浪小), 信区: C_and_CPP
标  题: 通过其他任何名字
发信站: 哈工大紫丁香 (2003年06月17日15:41:37 星期二), 站内信件

通过任何其他名字 
morningsing 翻译

-------------------------------------------------------------------------------
-

悬在几乎一百公里以外的天空,那个小小的行星占据了观察孔视野相当大的部分。它的表
面看起来好像玻璃碎裂后又用冰冻的胶水修补过。许多冻结的河流蜿蜒在十字形的低矮山
脉之间。荒凉的木卫二是那样的美轮美奂。  

扬声器传出“上船,第三和第七区”,然后又恢复沉默。  

珍妮和我在观察口边上和一群专家挤作一团,我们中的大多数看起来像是瞠目结舌的年轻
宇航员。可我们不在乎。当然,在飞船中的任何地方都可以看到更好的景色,但当看到真
实、原原本本的光直接从月球反射进你的眼睛时,总能有一丝特殊的感觉。  

“真是个好地方,” 珍妮自告奋勇的说。“他们将消息封锁得紧紧的,现在也还这样。”
 

“紧紧的?他们应该告诉我们真正的目的地。” 

“不会。我相信我们会很快找出他们在冰层底下发现了什么。”  

“嗯?嗯。”我回答,“仅仅来到这里,我就很高兴。当我以前看到这里的图片,还以为
是处理过的幻境,没想到真的这么美——不,比图片更美。我们真的来到这里了。这使我
想起自己的第一份工作。”  

她开玩笑地哼了一声。“任何东西都会使你想起第一份工作。” 

“我还年轻,所以容易受到感染。无论如何,刚才关于这次旅行的谈论又使我想起了它。
” 

“啊。”她停顿了一下。“你到底想说什么?” 

我笑了,接下去解释……  


-------------------------------------------------------------------------------
-

我曾经为一个设计问题苦苦挣扎。我真的希望自己解决,因为见习期即将结束,我希望证
明自己。  

你记得毕业后的第一份工作,对吧?正确地处理事情是多么的重要。我一直记得新进公司
的同事们,因为无法与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军官时拥有不同的行为。虚拟函数恰好合适,在P
ersonnel或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.Vis
it会以正确派生类型调用,而且重载解析过程会选出正确地函数。这就像加入了一个新的
虚函数,是么?”  

“的确是的,我的孩子。新加入的函数通常只需占用客户类的公共接口,但在其他任何方
面都像虚函数一样,即使它并非成员函数。所需的一切只是初始类体系支持Accept,以使
自己能够扩展。如果将来我们发现经常用新的子类扩展Personnel体系,或者希望更好地管
理编译依赖,可以移植到Acyclic Visitor——除了稍许修改Accept函数,完全不必改变Pe
rsonnel及其基类。[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. 
 
--


欢迎到C_and_CPP讨论相关问题。

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.239.80]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:3.563毫秒