📄 9474.htm
字号:
<P> ...</P>
<P>};</P>
<P><BR>class MyPerson: public Person, // 注意,使用了<BR> private PersonInfo { // 多继承<BR>public:<BR> MyPerson(DatabaseID pid): PersonInfo(pid) {}</P>
<P> // 继承来的虚分隔符函数的重新定义<BR> const char * valueDelimOpen() const { return ""; }<BR> const char * valueDelimClose() const { return ""; }</P>
<P> // 所需的Person成员函数的实现<BR> string name() const<BR> { return PersonInfo::theName(); }</P>
<P> string birthDate() const<BR> { return PersonInfo::theBirthDate(); }</P>
<P> string address() const<BR> { return PersonInfo::theAddress(); }</P>
<P> string nationality() const<BR> { return PersonInfo::theNationality(); }<BR>};</P>
<P>用图形表示,看起来象下面这样:</P>
<P> Person PersonInfo<BR> \ /<BR> \ /<BR> \/<BR> MyPerson</P>
<P>这种例子证明,MI会既有用又易于理解,尽管可怕的钻石形状继承图不会明显消失。</P>
<P>然而,必须当心诱惑。有时你会掉进这样的陷阱中:对某个需要改动的继承层次结构来说,本来用一个更基本的重新设计可以更好,但你却为了追求速度而去使用MI。例如,假设为可以活动的卡通角色设计一个类层次结构。至少从概念上来说,让各种角色能跳舞唱歌将很有意义,但每一种角色执行这些动作时方式都不一样。另外,跳舞唱歌的缺省行为是什么也不做。</P>
<P>所有这些用C++来表示就象这样:</P>
<P>class CartoonCharacter {<BR>public:<BR> virtual void dance() {}<BR> virtual void sing() {}<BR>};</P>
<P>虚函数自然地体现了这样的约束:唱歌跳舞对所有CartoonCharacter对象都有意义。什么也不做的缺省行为通过类中那些函数的空定义来表示(参见条款36)。假设有一个特殊类型的卡通角色是蚱蜢,它以自己特殊的方式跳舞唱歌:</P>
<P>class Grasshopper: public CartoonCharacter {<BR>public:<BR> virtual void dance(); // 定义在别的什么地方<BR> virtual void sing(); // 定义在别的什么地方<BR>};</P>
<P>现在假设,在实现了Grasshopper类后,你又想为蟋蟀增加一个类:</P>
<P>class Cricket: public CartoonCharacter {<BR>public:<BR> virtual void dance();<BR> virtual void sing();<BR>};</P>
<P>当坐下来实现Cricket类时,你意识到,为Grasshopper类所写的很多代码可以重复使用。但这需要费点神,因为要到各处去找出蚱蜢和蟋蟀唱歌跳舞的不同之处。你猛然间想出了一个代码复用的好办法:你准备用Grasshopper类来实现Cricket类,你还准备使用虚函数以使Cricket类可以定制Grasshopper的行为。</P>
<P>你立即认识到这两个要求 ---- "用...来实现" 的关系,以及重新定义虚函数的能力 ---- 意味着Cricket必须从Grasshopper私有继承,但蟋蟀当然还是一个卡通角色,所以你通过同时从Grasshopper和CartoonCharacter继承来重新定义Cricket:</P>
<P>class Cricket: public CartoonCharacter,<BR> private Grasshopper {<BR>public:<BR> virtual void dance();<BR> virtual void sing();<BR>};</P>
<P>然后准备对Grasshopper类做必要的修改。特别是,需要声明一些新的虚函数让Cricket重新定义:</P>
<P>class Grasshopper: public CartoonCharacter {<BR>public:<BR> virtual void dance();<BR> virtual void sing();</P>
<P>protected:<BR> virtual void danceCustomization1();<BR> virtual void danceCustomization2();</P>
<P> virtual void singCustomization();<BR>};</P>
<P>蚱蜢跳舞现在被定义成象这样:</P>
<P>void Grasshopper::dance()<BR>{<BR> 执行共同的跳舞动作;</P>
<P> danceCustomization1();</P>
<P> 执行更多共同的跳舞动作;</P>
<P> danceCustomization2();</P>
<P> 执行最后共同的跳舞动作;<BR>}</P>
<P>蚱蜢唱歌的设计与此类似。</P>
<P>很明显,Cricket类必须修改一下,因为它必须重新定义新的虚函数:</P>
<P>class Cricket:public CartoonCharacter,<BR> private Grasshopper {<BR>public:<BR> virtual void dance() { Grasshopper::dance(); }<BR> virtual void sing() { Grasshopper::sing(); }</P>
<P>protected:<BR> virtual void danceCustomization1();<BR> virtual void danceCustomization2();</P>
<P> virtual void singCustomization();<BR>};</P>
<P>这看来很不错。当需要Cricket对象去跳舞时,它执行Grasshopper类中共同的dance代码,然后执行Cricket类中定制的dance代码,接着继续执行Grasshopper::dance中的代码,等等。</P>
<P>然而,这个设计中有个严重的缺陷,这就是,你不小心撞上了 "奥卡姆剃刀" ---- 任何一种奥卡姆剃刀都是有害的思想,William of Occam的尤其如此。奥卡姆者鼓吹:如果没有必要,就不要增加实体。现在的情况下,实体就是指的继承关系。如果你相信多继承比单继承更复杂的话(我希望你相信),Cricket类的设计就没必要复杂。(译注:1) William of Occam(1285-1349),英国神学家,哲学家。2) 奥卡姆剃刀(Occam's razor)是一种思想,主要由William of Occam提出。之所以将它称为 "奥卡姆剃刀",是因为William of Occam经常性地、很锐利地运用这一思想。)</P>
<P>问题的根本之处在于,Cricket类和Grasshopper类之间并非 "用...来实现" 的关系。而是,Cricket类和Grasshopper类之间享有共同的代码。特别是,它们享有决定唱歌跳舞行为的代码 ---- 蚱蜢和蟋蟀都有这种共同的行为。</P>
<P>说两个类具有共同点的方式不是让一个类从另一个类继承,而是让它们都从一个共同的基类继承,蚱蜢和蟋蟀之间的公共代码不属于Grasshopper类,也不属于Cricket,而是属于它们共同的新的基类,如,Insect:</P>
<P>class CartoonCharacter { ... };</P>
<P>class Insect: public CartoonCharacter {<BR>public:<BR> virtual void dance(); // 蚱蜢和蟋蟀<BR> virtual void sing(); // 的公共代码</P>
<P>protected:<BR> virtual void danceCustomization1() = 0;<BR> virtual void danceCustomization2() = 0;</P>
<P> virtual void singCustomization() = 0;<BR>};</P>
<P>class Grasshopper: public Insect {<BR>protected:<BR> virtual void danceCustomization1();<BR> virtual void danceCustomization2();</P>
<P> virtual void singCustomization();<BR>};</P>
<P>class Cricket: public Insect {<BR>protected:<BR> virtual void danceCustomization1();<BR> virtual void danceCustomization2();</P>
<P> virtual void singCustomization();<BR>};</P>
<P> CartoonCharacter<BR> |<BR> |<BR> Insect<BR> /\<BR> / \<BR> / \<BR>Grasshopper Cricket</P>
<P>可以看到,这个设计更清晰。只是涉及到单继承,此外,只是用到了公有继承。Grasshopper和Cricket定义的只是定制功能;它们从Insect一点没变地继承了dance和sing函数。William of Occam一定会很骄傲。</P>
<P>尽管这个设计比采用了MI的那个方案更清晰,但初看可能会觉得比使用MI的还要逊色。毕竟,和MI的方案相比,这个单继承结构中引入了一个全新的类,而使用MI就不需要。如果没必要,为什么要引入一个额外的类呢?</P>
<P>这就将你带到了多继承诱人的本性面前。表面看来,MI好象使用起来更容易。它不需要增加新的类,虽然它要求在Grasshopper类中增加一些新的虚函数,但这些函数在任何情况下都是要增加的。</P>
<P>设想有个程序员正在维护一个大型C++类库,现在需要在库中增加一个新的类,就象Cricket类要被增加到现有的的CartoonCharacter/Grasshopper层次结构中一样。程序员知道,有大量的用户使用现有的层次结构,所以,库的变化越大,对用户的影响越大。程序员决心将这种影响降低到最小。对各种选择再三考虑之后,程序员认识到,如果增加一个从Grasshopper到Cricket的私有继承连接,层次结构中将不需要任何其它变化。程序员不禁因为这个想法露出了微笑,暗自庆幸今后可以大量地增加功能,而代价仅仅只是增加很小一点复杂性。</P>
<P>现在设想这个负责维护的程序员是你。那么,请抵御这一诱惑!</P><br><br>
</DIV></div></div>
</center></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -