亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

Effective C++ 2e Item43

2019-09-10 09:07:14
字體:
供稿:網(wǎng)友

條款43: 明智地使用多繼承

要看是誰來說,多繼承(MI)要么被認(rèn)為是神來之筆,要么被當(dāng)成是魔鬼的造物。支持者宣揚(yáng)說,它是對真實(shí)世界問題進(jìn)行自然模型化所必需的;而批評者爭論說,它太慢,難以實(shí)現(xiàn),功能卻不比單繼承強(qiáng)大。更讓人為難的是,面向?qū)ο缶幊陶Z言領(lǐng)域在這個問題上至今仍存在分歧:C++,Eiffel和the Common LISP Object System (CLOS)提供了MI;Smalltalk,Objective C和Object Pascal沒有提供;而Java只是提供有限的支持。可憐的程序員該相信誰呢?

在相信任何事情之前,首先得弄清事實(shí)。C++中,關(guān)于MI一條不容爭辯的事實(shí)是,MI的出現(xiàn)就象打開了潘朵拉的盒子,帶來了單繼承中絕對不會存在的復(fù)雜性。其中,最基本的一條是二義性(參見條款26)。如果一個派生類從多個基類繼承了一個成員名,所有對這個名字的訪問都是二義的;你必須明確地說出你所指的是哪個成員。下面的例子取自ARM(參見條款50)中的一個專題討論:

class Lottery {
public:
 virtual int draw();

 ...

};

class GraphicalObject {
public:
 virtual int draw();

 ...

};

class LotterySimulation: public Lottery,
/t/t/t public GraphicalObject {

 .../t/t/t  // 沒有聲明draw

};

LotterySimulation *pls = new LotterySimulation;

pls->draw();/t/t   // 錯誤! ---- 二義
pls->Lottery::draw();/t  // 正確
pls->GraphicalObject::draw();  // 正確

這段代碼看起來很笨拙,但起碼可以工作。遺憾的是,想避免這種笨拙很難。即使其中一個被繼承的draw函數(shù)是私有成員從而不能被訪問,二義還是存在。(對此有一個很好的理由來解釋,但完整的說明在條款26中提供,所以此處不再重復(fù)。)

顯式地限制修飾成員不僅很笨拙,而且還帶來限制。當(dāng)顯式地用一個類名來限制修飾一個虛函數(shù)時,函數(shù)的行為將不再具有虛擬的特征。相反,被調(diào)用的函數(shù)只能是你所指定的那個,即使調(diào)用是作用在派生類的對象上:

class SpecialLotterySimulation: public LotterySimulation {
public:
 virtual int draw();

 ...

};

pls = new SpecialLotterySimulation;

pls->draw();/t/t        // 錯誤! ---- 還是有二義
pls->Lottery::draw();/t       // 調(diào)用Lottery::draw
pls->GraphicalObject::draw();       // 調(diào)用GraphicalObject::draw

注意,在這種情況下,即使pls指向的是SpecialLotterySimulation對象,也無法(沒有 "向下轉(zhuǎn)換" ---- 參見條款39)調(diào)用這個類中定義的draw函數(shù)。

沒完,還有呢。Lottery和GraphicalObject中的draw函數(shù)都被聲明為虛函數(shù),所以子類可以重新定義它們(見條款36),但如果LotterySimulation想對二者都重新定義那該怎么辦?令人沮喪的是,這不可能,因為一個類只允許有唯一一個沒有參數(shù)、名稱為draw的函數(shù)。(這個規(guī)則有個例外,即一個函數(shù)為const而另一個不是的時候 ---- 見條款21)

從某一方面來說,這個問題很嚴(yán)重,嚴(yán)重到足以成為修改C++語言的理由。ARM中就討論了一種可能,即,允許被繼承的虛函數(shù)可以 "改名" ;但后來又發(fā)現(xiàn),可以通過增加一對新類來巧妙地避開這個問題:

class AuxLottery: public Lottery {
public:
 virtual int lotteryDraw() = 0;

 virtual int draw() { return lotteryDraw(); }
};

class AuxGraphicalObject: public GraphicalObject {
public:
 virtual int graphicalObjectDraw() = 0;

 virtual int draw() { return graphicalObjectDraw(); }
};


class LotterySimulation: public AuxLottery,
/t/t/t public AuxGraphicalObject {
public:
 virtual int lotteryDraw();
 virtual int graphicalObjectDraw();

 ...

};

這兩個新類, AuxLottery和AuxGraphicalObject,本質(zhì)上為各自繼承的draw函數(shù)聲明了新的名字。新名字以純虛函數(shù)的形式提供,本例中即lotteryDraw和graphicalObjectDraw;函數(shù)是純虛擬的,所以具體的子類必須重新定義它們。另外,每個類都重新定義了繼承而來的draw函數(shù),讓它們調(diào)用新的純虛函數(shù)。最終效果是,在這個類體系結(jié)構(gòu)中,有二義的單個名字draw被有效地分成了無二義但功能等價的兩個名字:lotteryDraw和graphicalObjectDraw:

LotterySimulation *pls = new LotterySimulation;

Lottery *pl = pls;
GraphicalObject *pgo = pls;

// 調(diào)用LotterySimulation::lotteryDraw
pl->draw();

// 調(diào)用LotterySimulation::graphicalObjectDraw
pgo->draw();

這是一個集純虛函數(shù),簡單虛函數(shù)和內(nèi)聯(lián)函數(shù)(參見條款33)綜合應(yīng)用之大成的方法,值得牢記在心。首先,它解決了問題,這個問題說不定哪天你就會碰到。其次,它可以提醒你,使用多繼承會導(dǎo)致復(fù)雜性。是的,這個方法解決了問題,但僅僅為了重新定義一個虛函數(shù)而不得不去引入新的類,你真的愿意這樣做嗎?AuxLottery和AuxGraphicalObject類對于保證類層次結(jié)構(gòu)的正確運(yùn)轉(zhuǎn)是必需的,但它們既不對應(yīng)于問題范疇(problem domain )的某個抽象,也不對應(yīng)于實(shí)現(xiàn)范疇(implementation domain)的某個抽象。它們單純是作為一種實(shí)現(xiàn)設(shè)備而存在,再沒有別的用處。你一定知道,好的軟件是 "設(shè)備無關(guān)" 的,這條法則在此也適用。

將來使用MI還會面臨更多的問題,二義性問題(盡管有趣)只不過是剛開始。另一個問題基于這樣一個實(shí)踐經(jīng)驗:一個起初象下面這樣的繼承層次結(jié)構(gòu):

class B { ... };
class C { ... };
class D: public B, public C { ... };

/t/t    B    C
/t/t         /
/t/t        /
/t/t       /
/t/t       D

往往最后悲慘地發(fā)展成象下面這樣:

class A { ... };
class B : virtual public A { ... };
class C : virtual public A { ... };
class D: public B, public C { ... };

/t/t       A  
/t/t       /
/t/t      /  
/t/t     /    
/t/t    B    C
/t/t         /
/t/t        /
/t/t       /
/t/t       D

鉆石可能是女孩最好的朋友,也許不是;但肯定的是,象這樣一種鉆石形狀的繼承結(jié)構(gòu)絕對不可能成為我們的朋友。如果創(chuàng)建了象這樣的層次結(jié)構(gòu),就會立即面臨這樣一個問題:是不是該讓A成為虛基類呢?即,從A的繼承是否應(yīng)該是虛擬的呢?現(xiàn)實(shí)中,答案幾乎總是 ---- 應(yīng)該;只有極少數(shù)情況下會想讓類型D的對象包含A的數(shù)據(jù)成員的多個拷貝。正是認(rèn)識到這一事實(shí),上面的B和C將A聲明為虛基類。

遺憾的是,在定義B和C的時候,你可能不知道將來是否會有類去同時繼承它們,而且知不知道這一點(diǎn)實(shí)際上對正確地定義這兩個類沒有必要。對類的設(shè)計者來說,這實(shí)在是進(jìn)退兩難。如果不將A聲明為B和C的虛基類,今后D的設(shè)計者就有可能需要修改B和C的定義,以便更有效地使用它們。通常,這很難做到,因為A,B和C的定義往往是只讀的。例如這樣的情況:A,B和C在一個庫中,而D由庫的用戶來寫。

另一方面,如果真的將A聲明為B和C的虛基類,往往會在空間和時間上強(qiáng)加給用戶額外的開銷。因為虛基類常常是通過對象指針來實(shí)現(xiàn)的,并非對象本身。自不必說,內(nèi)存中對象的分布是和編譯器相關(guān)的,但一條不變的事實(shí)是:如果A作為 "非虛" 基類,類型D的對象在內(nèi)存中的分布通常占用連續(xù)的內(nèi)存單元;如果A作為 "虛" 基類,有時,類型D的對象在內(nèi)存中的分布占用連續(xù)的內(nèi)存單元,但其中兩個單元包含的是指針,指向包含虛基類數(shù)據(jù)成員的內(nèi)存單元:

A是非虛基類時D對象通常的內(nèi)存分布:

/t   A部分+ B部分+ A部分 + C部分 + D部分

A是虛基類時D對象在某些編譯器下的內(nèi)存分布:

/t/t/t   ------------------------------------------------
/t/t/t   |/t/t/t/t/t/t   |
/t/t/t   |/t/t/t/t/t/t  +
/t  B部分 + 指針 + C部分 + 指針 + D部分 + A部分
/t/t/t/t/t/t      |/t/t       +
/t/t/t/t/t/t      |/t/t        |
/t/t/t/t/t/t      ------------------------

即使編譯器不采用這種特殊的實(shí)現(xiàn)策略,使用虛繼承通常也會帶來某種空間上的懲罰。

考慮到這些因素,看來,在進(jìn)行高效的類設(shè)計時如果涉及到MI,作為庫的設(shè)計者就要具有超凡的遠(yuǎn)見。然而現(xiàn)在的年代,常識都日益成為了稀有品,因而你會不明智地過多依賴于語言特性,這就不僅要求設(shè)計者能夠預(yù)計得到未來的需要,而且簡直就是要你做到徹底的先知先覺(參見條款M32)。

當(dāng)然,這也可以說成是在虛函數(shù)和非虛函數(shù)間選擇,但還是有重大的不同。條款36說明,虛函數(shù)具有定義明確的高級含義,非虛函數(shù)也同樣具有定義明確的高級含義,而且它們的含義有顯著的不同,所以在清楚自己想對子類的設(shè)計者傳達(dá)什么含義的基礎(chǔ)上,在二者之間作出選擇是可能的。但是,決定基類是否應(yīng)該是虛擬的,則缺乏定義明確的高級含義;相反,決定通常取決于整個繼承的層次結(jié)構(gòu),所以除非知道了整個層次結(jié)構(gòu),否則無法做出決定。如果正確地定義出個類之前需要清楚地知道將來怎么使用它,這種情況下將很難設(shè)計出高效的類。

就算避開了二義性問題,并且解決了是否應(yīng)該從基類虛擬繼承的疑問,還是會有許多復(fù)雜性問題等著你。為了長話短說,在此我僅提出應(yīng)該記住的其它兩點(diǎn):

? 向虛基類傳遞構(gòu)造函數(shù)參數(shù)。非虛繼承時,基類構(gòu)造函數(shù)的參數(shù)是由緊臨的派生類的成員初始化列表指定的。因為單繼承的層次結(jié)構(gòu)只需要非虛基類,繼承層次結(jié)構(gòu)中參數(shù)的向上傳遞采用的是一種很自然的方式:第n層的類將參數(shù)傳給第n-1層的類。但是,虛基類的構(gòu)造函數(shù)則不同,它的參數(shù)是由繼承結(jié)構(gòu)中最底層派生類的成員初始化列表指定的。這就造成,負(fù)責(zé)初始化虛基類的那個類可能在繼承圖中和它相距很遠(yuǎn);如果有新類增加到繼承結(jié)構(gòu)中,執(zhí)行初始化的類還可能改變。(避免這個問題的一個好辦法是:消除對虛基類傳遞構(gòu)造函數(shù)參數(shù)的需要。最簡單的做法是避免在這樣的類中放入數(shù)據(jù)成員。這本質(zhì)上是Java的解決之道:Java中的虛基類(即,"接口")禁止包含數(shù)據(jù))

? 虛函數(shù)的優(yōu)先度。就在你自認(rèn)為弄清了所有的二義之時,它們卻又在你面前搖身一變。再次看看關(guān)于類A,B,C和D的鉆石形狀的繼承圖。假設(shè)A定義了一個虛成員函數(shù)mf,C重定義了它;B和D則沒有重定義mf:

/t     A   virtual void mf();
/t     /
/t    /  
/t   /    
/t  B    C   virtual void mf();
/t       /
/t      /
/t     /
/t     D

根據(jù)以前的討論,你會認(rèn)為下面有二義:

D *pd = new D;
pd->mf();/t/t      // A::mf或者C::mf?

該為D的對象調(diào)用哪個mf呢,是直接從C繼承的還是間接(通過B)從A繼承的那個呢?答案取決于B和C如何從A繼承。具體來說,如果A是B或C的非虛基類,調(diào)用具有二義性;但如果A是B和C的虛基類,就可以說C中mf的重定義優(yōu)先度高于最初A中的定義,因而通過pd對mf的調(diào)用將(無二義地)解析為C::mf。如果你坐下來仔細(xì)想想,這正是你想要的行為;但需要坐下仔細(xì)想想才能弄懂,也確實(shí)是一種痛苦。

也許至此你會承認(rèn)MI確實(shí)會導(dǎo)致復(fù)雜化。也許你認(rèn)識到每個人其實(shí)都不想使用它。也許你準(zhǔn)備建議國際C++標(biāo)準(zhǔn)委員會將多繼承從語言中去掉;或者至少你想向你的老板建議,全公司的程序員都禁止使用它。

也許你太性急了。

請記住,C++的設(shè)計者并沒有想讓多繼承難以使用;恰恰是,想讓一切都能以更合理的方式協(xié)調(diào)工作,這本身會帶來某些復(fù)雜性。上面的討論中你會注意到,這些復(fù)雜性很多是由于使用虛基類引起的。如果能避免使用虛基類 ---- 即,如果能避免產(chǎn)生那種致命的鉆石形狀繼承圖 ---- 事情就好處理多了。

例如,條款34中講到,協(xié)議類(Protocol class)的存在僅僅是為派生類制定接口;它沒有數(shù)據(jù)成員,沒有構(gòu)造函數(shù),有一個虛析構(gòu)函數(shù)(參見條款14),有一組用來指定接口的純虛函數(shù)。一個Person協(xié)議類看起來象下面這樣:

class Person {
public:
 virtual ~Person();

 virtual string name() const = 0;
 virtual string birthDate() const = 0;
 virtual string address() const = 0;
 virtual string nationality() const = 0;
};

這個類的用戶在編程時必須使用Person的指針或引用,因為抽象類不能被實(shí)例化。

為了創(chuàng)建 "可以作為Person對象而使用" 的對象,Person的用戶使用工廠函數(shù)(factory function,參見條款34)來實(shí)例化具體的子類:

// 工廠函數(shù),從一個唯一的數(shù)據(jù)庫ID
// 創(chuàng)建一個Person對象
Person * makePerson(DatabaseID personIdentifier);

DatabaseID askUserForDatabaseID();


DatabaseID pid = askUserForDatabaseID();

Person *pp = makePerson(pid);    // 創(chuàng)建支持Person
/t/t/t/t // 接口的對象

.../t/t/t      // 通過Person的成員函數(shù)
/t/t/t/t // 操作*pp

delete pp;/t/t       // 刪除不再需要的對象

這就帶來一個問題:makePerson返回的指針?biāo)赶虻膶ο笕绾蝿?chuàng)建呢?顯然,必須從Person派生出某種具體類,使得makePerson可以對其進(jìn)行實(shí)例化。

假設(shè)這個類被稱為MyPerson。作為一個具體類,MyPerson必須實(shí)現(xiàn)從Person繼承而來的純虛函數(shù)。這可以從零做起,但如果已經(jīng)存在一些組件可以完成大多數(shù)或全部所需的工作,那么從軟件工程的角度來說,能利用這些組件將再好不過。例如,假設(shè)已經(jīng)有一個和數(shù)據(jù)庫有關(guān)的舊類PersonInfo,它提供的功能正是MyPerson所需要的:

class PersonInfo {
public:
 PersonInfo(DatabaseID pid);
 virtual ~PersonInfo();

 virtual const char * theName() const;
 virtual const char * theBirthDate() const;
 virtual const char * theAddress() const;
 virtual const char * theNationality() const;

 virtual const char * valueDelimOpen() const;       // 看下文
 virtual const char * valueDelimClose() const;    

 ...

};

可以斷定這是一個很舊的類,因為它的成員函數(shù)返回的是const char*而不是string對象。但是,如果鞋合腳,為什么不穿呢?這個類的成員函數(shù)名暗示,這雙鞋穿上去會很舒服。

隨之你會發(fā)現(xiàn),當(dāng)初設(shè)計PersonInfo是用來方便地以各種不同格式打印數(shù)據(jù)庫字段,每個字段值的開頭和結(jié)尾用特殊字符串分開。默認(rèn)情況下,字段值的起始分隔符和結(jié)束分隔符為括號,所以字段值 "Ring-tailed Lemur" 將會這樣被格式化:

[Ring-tailed Lemur]

因為括號不是所有PersonInfo的用戶都想要的,虛函數(shù)valueDelimOpen和valueDelimClose允許派生類指定它們自己的起始分隔符和結(jié)束分隔符。PersonInfo類的theName,theBirthDate,theAddress以及theNationality的實(shí)現(xiàn)將調(diào)用這兩個虛函數(shù),在它們的返回值中添加適當(dāng)?shù)姆指舴?。拿PersonInfo::name作為例子,代碼看起來象這樣:

const char * PersonInfo::valueDelimOpen() const
{
 return "[";/t/t   // 默認(rèn)起始分隔符
}

const char * PersonInfo::valueDelimClose() const
{
 return "]";/t/t   // 默認(rèn)結(jié)束分隔符
}

const char * PersonInfo::theName() const
{
 // 為返回值保留緩沖區(qū)。因為是靜態(tài)
 // 類型,它被自動初始化為全零。
 static char value[MAX_FORMATTED_FIELD_VALUE_LENGTH];

 // 寫起始分隔符
 strcpy(value, valueDelimOpen());

 將對象的名字字段值添加到字符串中

 // 寫結(jié)束分隔符
 strcat(value, valueDelimClose());

 return value;
}

有些人會挑剔PersonInfo::theName的設(shè)計(特別是使用了固定大小的靜態(tài)緩沖區(qū) ---- 參見條款23),但請將你的挑剔放在一邊,關(guān)注這一點(diǎn):首先,theName調(diào)用valueDelimOpen,生成它將要返回的字符串的起始分隔符;然后,生成名字值本身;最后,調(diào)用valueDelimClose。因為valueDelimOpen和valueDelimClose是虛函數(shù),theName返回的結(jié)果既依賴于PersonInfo,也依賴于從PersonInfo派生的類。

作為MyPerson的實(shí)現(xiàn)者,這是條好消息,因為在研讀Person文檔的細(xì)則時你發(fā)現(xiàn),name及其相關(guān)函數(shù)需要返回的是不帶修飾的值,即,不允許帶分隔符。也就是說,如果一個人來自Madagascar,調(diào)用這個人的nationality函數(shù)將返回"Madagascar",而不是 "[Madagascar]"。

MyPerson和PersonInfo之間的關(guān)系是,PersonInfo剛好有些函數(shù)使得MyPerson易于實(shí)現(xiàn)。僅次而已。沒看到有 "是一個" 或 "有一個" 的關(guān)系。它們的關(guān)系是 "用...來實(shí)現(xiàn)",而且我們知道,這可以用兩種方式來表示:通過分層(見條款40)和通過私有繼承(見條款42)。條款42指出,分層一般來說是更好的方法,但在有虛函數(shù)要被重新定義的情況下,需要使用私有繼承?,F(xiàn)在的情況是,MyPerson需要重新定義valueDelimOpen和valueDelimClose,所以不能用分層,而必須用私有繼承:MyPerson必須從PersonInfo私有繼承。

但MyPerson還必須實(shí)現(xiàn)Person接口,因而需要公有繼承。這導(dǎo)致了多繼承一個很合理的應(yīng)用:將接口的公有繼承和實(shí)現(xiàn)的私有繼承結(jié)合起來:

class Person {/t/t        // 這個類指定了
public:/t/t/t       // 需要被實(shí)現(xiàn)
 virtual ~Person();/t/t  // 的接口

 virtual string name() const = 0;
 virtual string birthDate() const = 0;
 virtual string address() const = 0;
 virtual string nationality() const = 0;
};

class DatabaseID { ... };/t     // 被后面的代碼使用;
/t/t/t/t      // 細(xì)節(jié)不重要

class PersonInfo {/t/t    // 這個類有些有用
public:/t/t/t       // 的函數(shù),可以用來
 PersonInfo(DatabaseID pid);/t // 實(shí)現(xiàn)Person接口
 virtual ~PersonInfo();

 virtual const char * theName() const;
 virtual const char * theBirthDate() const;
 virtual const char * theAddress() const;
 virtual const char * theNationality() const;

 virtual const char * valueDelimOpen() const;
 virtual const char * valueDelimClose() const;

 ...

};


class MyPerson: public Person,        // 注意,使用了
/t        private PersonInfo {  // 多繼承
public:
 MyPerson(DatabaseID pid): PersonInfo(pid) {}

 // 繼承來的虛分隔符函數(shù)的重新定義
 const char * valueDelimOpen() const { return ""; }
 const char * valueDelimClose() const { return ""; }

 // 所需的Person成員函數(shù)的實(shí)現(xiàn)
 string name() const
 { return PersonInfo::theName(); }

 string birthDate() const
 { return PersonInfo::theBirthDate(); }

 string address() const
 { return PersonInfo::theAddress(); }

 string nationality() const
 { return PersonInfo::theNationality(); }
};

用圖形表示,看起來象下面這樣:

/t Person       PersonInfo
/t/t         /
/t/t        /
/t/t       /
/t/t   MyPerson

這種例子證明,MI會既有用又易于理解,盡管可怕的鉆石形狀繼承圖不會明顯消失。

然而,必須當(dāng)心誘惑。有時你會掉進(jìn)這樣的陷阱中:對某個需要改動的繼承層次結(jié)構(gòu)來說,本來用一個更基本的重新設(shè)計可以更好,但你卻為了追求速度而去使用MI。例如,假設(shè)為可以活動的卡通角色設(shè)計一個類層次結(jié)構(gòu)。至少從概念上來說,讓各種角色能跳舞唱歌將很有意義,但每一種角色執(zhí)行這些動作時方式都不一樣。另外,跳舞唱歌的缺省行為是什么也不做。

所有這些用C++來表示就象這樣:

class CartoonCharacter {
public:
 virtual void dance() {}
 virtual void sing() {}
};

虛函數(shù)自然地體現(xiàn)了這樣的約束:唱歌跳舞對所有CartoonCharacter對象都有意義。什么也不做的缺省行為通過類中那些函數(shù)的空定義來表示(參見條款36)。假設(shè)有一個特殊類型的卡通角色是蚱蜢,它以自己特殊的方式跳舞唱歌:

class Grasshopper: public CartoonCharacter {
public:
 virtual void dance();    // 定義在別的什么地方
 virtual void sing();     // 定義在別的什么地方
};

現(xiàn)在假設(shè),在實(shí)現(xiàn)了Grasshopper類后,你又想為蟋蟀增加一個類:

class Cricket: public CartoonCharacter {
public:
 virtual void dance();
 virtual void sing();
};

當(dāng)坐下來實(shí)現(xiàn)Cricket類時,你意識到,為Grasshopper類所寫的很多代碼可以重復(fù)使用。但這需要費(fèi)點(diǎn)神,因為要到各處去找出蚱蜢和蟋蟀唱歌跳舞的不同之處。你猛然間想出了一個代碼復(fù)用的好辦法:你準(zhǔn)備用Grasshopper類來實(shí)現(xiàn)Cricket類,你還準(zhǔn)備使用虛函數(shù)以使Cricket類可以定制Grasshopper的行為。

你立即認(rèn)識到這兩個要求 ---- "用...來實(shí)現(xiàn)" 的關(guān)系,以及重新定義虛函數(shù)的能力 ---- 意味著Cricket必須從Grasshopper私有繼承,但蟋蟀當(dāng)然還是一個卡通角色,所以你通過同時從Grasshopper和CartoonCharacter繼承來重新定義Cricket:

class Cricket: public CartoonCharacter,
/t       private Grasshopper {
public:
 virtual void dance();
 virtual void sing();
};

然后準(zhǔn)備對Grasshopper類做必要的修改。特別是,需要聲明一些新的虛函數(shù)讓Cricket重新定義:

class Grasshopper: public CartoonCharacter {
public:
 virtual void dance();
 virtual void sing();

protected:
 virtual void danceCustomization1();
 virtual void danceCustomization2();

 virtual void singCustomization();
};

蚱蜢跳舞現(xiàn)在被定義成象這樣:

void Grasshopper::dance()
{
 執(zhí)行共同的跳舞動作;

 danceCustomization1();

 執(zhí)行更多共同的跳舞動作;

 danceCustomization2();

 執(zhí)行最后共同的跳舞動作;
}

蚱蜢唱歌的設(shè)計與此類似。

很明顯,Cricket類必須修改一下,因為它必須重新定義新的虛函數(shù):

class Cricket:public CartoonCharacter,
     private Grasshopper {
public:
 virtual void dance() { Grasshopper::dance(); }
 virtual void sing() { Grasshopper::sing(); }

protected:
 virtual void danceCustomization1();
 virtual void danceCustomization2();

 virtual void singCustomization();
};

這看來很不錯。當(dāng)需要Cricket對象去跳舞時,它執(zhí)行Grasshopper類中共同的dance代碼,然后執(zhí)行Cricket類中定制的dance代碼,接著繼續(xù)執(zhí)行Grasshopper::dance中的代碼,等等。

然而,這個設(shè)計中有個嚴(yán)重的缺陷,這就是,你不小心撞上了 "奧卡姆剃刀" ---- 任何一種奧卡姆剃刀都是有害的思想,William of Occam的尤其如此。奧卡姆者鼓吹:如果沒有必要,就不要增加實(shí)體?,F(xiàn)在的情況下,實(shí)體就是指的繼承關(guān)系。如果你相信多繼承比單繼承更復(fù)雜的話(我希望你相信),Cricket類的設(shè)計就沒必要復(fù)雜。(譯注:1) William of Occam(1285-1349),英國神學(xué)家,哲學(xué)家。2) 奧卡姆剃刀(Occam's razor)是一種思想,主要由William of Occam提出。之所以將它稱為 "奧卡姆剃刀",是因為William of Occam經(jīng)常性地、很銳利地運(yùn)用這一思想。)

問題的根本之處在于,Cricket類和Grasshopper類之間并非 "用...來實(shí)現(xiàn)" 的關(guān)系。而是,Cricket類和Grasshopper類之間享有共同的代碼。特別是,它們享有決定唱歌跳舞行為的代碼 ---- 蚱蜢和蟋蟀都有這種共同的行為。

說兩個類具有共同點(diǎn)的方式不是讓一個類從另一個類繼承,而是讓它們都從一個共同的基類繼承,蚱蜢和蟋蟀之間的公共代碼不屬于Grasshopper類,也不屬于Cricket,而是屬于它們共同的新的基類,如,Insect:

class CartoonCharacter { ... };

class Insect: public CartoonCharacter {
public:
 virtual void dance();    // 蚱蜢和蟋蟀
 virtual void sing();     // 的公共代碼

protected:
 virtual void danceCustomization1() = 0;
 virtual void danceCustomization2() = 0;

 virtual void singCustomization() = 0;
};

class Grasshopper: public Insect {
protected:
 virtual void danceCustomization1();
 virtual void danceCustomization2();

 virtual void singCustomization();
};

class Cricket: public Insect {
protected:
 virtual void danceCustomization1();
 virtual void danceCustomization2();

 virtual void singCustomization();
};

/t   CartoonCharacter
/t/t        |
/t/t        |
/t/t   Insect
/t/t       /
/t/t      /  
/t/t     /    
Grasshopper     Cricket

可以看到,這個設(shè)計更清晰。只是涉及到單繼承,此外,只是用到了公有繼承。Grasshopper和Cricket定義的只是定制功能;它們從Insect一點(diǎn)沒變地繼承了dance和sing函數(shù)。William of Occam一定會很驕傲。

盡管這個設(shè)計比采用了MI的那個方案更清晰,但初看可能會覺得比使用MI的還要遜色。畢竟,和MI的方案相比,這個單繼承結(jié)構(gòu)中引入了一個全新的類,而使用MI就不需要。如果沒必要,為什么要引入一個額外的類呢?

這就將你帶到了多繼承誘人的本性面前。表面看來,MI好象使用起來更容易。它不需要增加新的類,雖然它要求在Grasshopper類中增加一些新的虛函數(shù),但這些函數(shù)在任何情況下都是要增加的。

設(shè)想有個程序員正在維護(hù)一個大型C++類庫,現(xiàn)在需要在庫中增加一個新的類,就象Cricket類要被增加到現(xiàn)有的的CartoonCharacter/Grasshopper層次結(jié)構(gòu)中一樣。程序員知道,有大量的用戶使用現(xiàn)有的層次結(jié)構(gòu),所以,庫的變化越大,對用戶的影響越大。程序員決心將這種影響降低到最小。對各種選擇再三考慮之后,程序員認(rèn)識到,如果增加一個從Grasshopper到Cricket的私有繼承連接,層次結(jié)構(gòu)中將不需要任何其它變化。程序員不禁因為這個想法露出了微笑,暗自慶幸今后可以大量地增加功能,而代價僅僅只是增加很小一點(diǎn)復(fù)雜性。

現(xiàn)在設(shè)想這個負(fù)責(zé)維護(hù)的程序員是你。那么,請抵御這一誘惑!

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表

圖片精選

国产精品理人伦一区二区三区| 久久免费少妇高潮久久精品99| 欧美性一级生活| 午夜一区二区三区不卡视频| 91美女片黄在线观| 天天看天天操| 黄色片子在线观看| 伊人网在线免费| 国产精品综合av一区二区国产馆| 国产精品一区二区免费福利视频| 国产精品99精品无码视亚| 欧美自拍第一页| 日本在线视频不卡| 自拍偷拍欧美激情| 日本免费高清一区| 四虎永久在线精品免费一区二区| 一本一道无码中文字幕精品热| p色视频免费在线观看| 在线色视频观看| 欧美午夜性春猛xxxx| 国产又爽又黄的激情精品视频| 亚洲在线国产日韩欧美| 91香蕉视频免费在线观看| 偷窥韩漫第三季| 麻豆映画在线观看| 国产成人无码av在线播放dvd| 色婷婷狠狠五月综合天色拍| 亚洲自拍另类欧美丝袜| 国产a级片视频| 久久无码av三级| 亚洲曰本av电影| 小说区图片区综合久久亚洲| 国产成人精品免费视频大全最热| 美女被男人操网站| 国内精彩免费自拍视频在线观看网址| 日韩中文娱乐网| 国产丝袜在线精品| 免费能直接在线观看黄的视频| 一级特黄aa大片| 99热最新网址| 99久久久免费精品| 欧美视频日韩视频在线观看| 日本精品久久久久久久久久| 久久久电影免费观看完整版| 在线亚洲日本| 亚洲一级在线观看| 国产夫妻性爱视频| 国产精品毛片久久久久久| 无码一区二区三区视频| 人妻aⅴ无码一区二区三区| 视频一区二区三区在线| 日本久久一区二区| 国产精品青草综合久久久久99| 成熟老妇女视频| 国产精品一区二区精品| 国产成人一二片| 香港久久久电影| 羞羞的视频网站| 免费黄色特级片| 在线观看日韩精品视频| 国产精品女主播视频| 国产精品久久久久久久久久久久久久久久久| 成人精品一区二区三区电影黑人| av高清在线观看| 日韩一卡二卡在线观看| 亚洲18在线看污www麻豆| 亚洲一级黄色录像| 亚欧美无遮挡hd高清在线视频| 精品99久久久久成人网站免费| 无码精品a∨在线观看中文| 国产高清在线观看视频| 青青在线免费观看| 欧美精品欧美精品系列c| 国产亚洲激情| 亚洲精品97久久久babes| 欧美在线观看视频一区二区| 久久久精品麻豆| 一区二区三区在线视频观看58| 欧美午夜电影在线观看| 亚洲精品九九| 黄色片视频在线免费观看| 国产suv精品一区二区6| 日韩a级大片| 国产不卡视频在线| 日韩专区在线| 一级日韩一级欧美| 日韩在线免费视频观看| 久一视频在线观看| 国产网友自拍视频导航网站在线观看| 中文字幕av免费在线观看| 青花影视在线观看免费高清| 九九在线观看视频| 亚洲日本精品一区| 亚洲天堂av一区二区三区| 黄色的毛片免费| 欧美日韩中文一区二区| 欧美日韩第一区| 一区二区三区不卡视频在线观看| 欧美激情视频一区二区三区免费| siro系绝美精品系列| 好看的av网站| 拍拍拍在线观看视频免费| 国产一区二区精品| 天堂va欧美ⅴa亚洲va一国产| 国产在线拍揄自揄视频不卡99| 一级毛片在线观| 亚洲色图 激情小说| 国产精品a成v人在线播放| 国产精品人成电影在线观看| 久久丁香综合五月国产三级网站| 国内一区二区视频| 你懂的网站在线观看网址| 久热这里只精品99re8久| 三日本三级少妇三级99| 国产人妖在线播放| 六月婷婷激情综合| 中文字幕亚洲综合久久菠萝蜜| 日韩欧美国产免费播放| 色棕色天天综合网| 欧美激情综合在线| 桃乃木香奈和黑人aⅴ在线播放| 精品久久久久久久人人人人传媒| xxxx在线播放| 婷婷无套内射影院| 黄网站app在线观看下载视频大全官网| 四虎av在线| 国产99午夜精品一区二区三区| 欧美亚韩一区| 成人av激情人伦小说| 欧美日韩中文字幕在线观看| 久久综合狠狠综合久久综青草| 成年人午夜免费视频| 久久99导航| 久久中文在线| 国产99久久久国产精品免费看| 6080亚洲理论片在线观看| 91九色在线视频| 国产一区二区三区久久悠悠色av| 顶级网黄在线播放| 国产最新自拍视频| 永久免费看mv网站入口亚洲| 亚洲精品视频一二三区| 成人福利电影精品一区二区在线观看| 亚洲一区二区影院| 久久影视一区| 在线视频精品| 成人动漫h在线观看| 国产精品久久久久久久久久久久久久久久久久| 毛片大全在线观看| 精品av中文字幕在线毛片| 久久女同互慰一区二区三区| 99久久精品久久久久久ai换脸| 国产精品www在线观看| 91福利在线播放| 午夜精品久久久久99蜜桃最新版| 欧美精品一区二区三区免费| 波多野结衣av在线观看| 免费网站免费进入在线| 国产欧美在线视频| 精品久久人妻av中文字幕| 欧美mv日韩mv国产网站app| 午夜在线一区二区| 天堂资源av| 美腿丝袜亚洲色图| 激情综合网俺也去| 在线国产福利网站| 波多野结衣一二三区| 欧美视频自拍偷拍| 午夜av噜噜噜噜噜噜| 亚洲婷婷影院| 欧美激情高清视频| 男人天堂资源在线| 国产成人精彩在线视频九色| 日韩免费观看高清| 亚洲精品一区二区三区蜜桃| 国内精品中文字幕| 一区二区三区欧美在线观看| 丁香六月久久综合狠狠色| 黄色一级视频在线观看| 午夜在线成人av| 天天色综合成人网| 黄色一区二区在线| 国产伦精品一区二区三毛| 黄色网址网站| 亚洲av无码专区在线| 亚洲一级电影视频| 性感美女一区二区三区| 国产视频一区二区三区四区五区| xfplay资源站夜色先锋5566| 一二区成人影院电影网| 992tv在线观看| 亚洲精品aaaaa| 精品国产人妻一区二区三区| 欧美日韩国产页| 福利电影一区二区三区| 在线免费视频一区二区| 女人av一区| 成人精品中文字幕| 尤物yw193can在线观看| 亚洲第一区在线| 在线观看h网址| 在线视频不卡一区二区三区| 五月天婷婷在线播放| 日韩精品极品视频| 一本大道av伊人久久综合| 日韩午夜电影网| 夜夜躁狠狠躁日日躁av| av日韩久久| 一区二区在线免费看| 精品国产免费一区二区三区| xxx一区二区| 欧美xxx久久| 亚洲视频播放| 欧美在线激情网| 91欧美极品| 久久久久久久久久久久久久| 禁久久精品乱码| 国产成人精品男人的天堂538| 国产亚洲欧美中文| 成年女人免费又黄又爽视频| 亚洲色图25p| 久久成人福利视频| 九色精品91| 国产成人女人毛片视频在线| 亚洲精品天天看| 一区二区免费在线| 久久久国产综合精品女国产盗摄| www.午夜av| 猛男gaygay欧美视频| 久久久久人妻一区精品色| 国产51人人成人人人人爽色哟哟| 二区三区在线| 一区二区三区国产视频| 亚洲欧美日韩国产成人精品影院| 久久亚洲影音av资源网| 狼狼综合久久久久综合网| 欧美在线免费| 欧美做受69| 国产999精品久久久| www.免费黄色| 中文字幕有码热在线视频| 粉嫩欧美一区二区三区| 国产激情偷乱视频一区二区三区| 四虎最新网站| 亚洲欧美成人一区二区在线电影| 99久久www免费| 97超级碰在线看视频免费在线看| 免费a在线观看播放| 美女福利视频在线| 国产精品午夜福利| 精品人妻伦九区久久aaa片| 国产一区二区三区天码| 99久久久国产| 亚洲成a人v欧美综合天堂| 激情欧美一区二区三区中文字幕| 免费视频一区二区| www.狠狠操.com| 黄色av小说在线观看| 欧美,日韩,国产在线| 国产高清视频在线观看| 九九热这里只有精品免费看| 国产精品久久久久久在线| 精品一区二区三区在线| 狠狠操狠狠干视频| 久草在线免费资源| 欧美日韩免费观看中文| 中文字幕成人在线| 91精品一区二区三区综合| 亚洲蜜臀av乱码久久精品| 久久av色综合| 在线免费观看的av| 亚洲综合网在线| 老熟妻内射精品一区| 69久久夜色精品国产7777| 午夜免费一区二区| 亚洲欧美日韩视频一区| 亚洲视频手机在线观看| 羞羞网站在线观看| 最近中文字幕免费mv视频多少集| 伦一区二区三区中文字幕v亚洲| 日韩电影第一页| 欧美肉体xxxx裸体137大胆| 亚洲五码在线| 天天操天天擦| 午夜国产一区二区| 久久久人成影片免费观看| 91高清视频免费| 天海翼一区二区三区免费| 日本高清一二三区| 欧美午夜精品| 久久精品成人一区二区三区| 亚洲福利在线观看视频| 欧美一卡2卡3卡4卡无卡免费观看水多多| 欧美重口另类videos人妖| 激情久久一区二区| 尤物网在线观看| 成人信息集中地欧美| 日韩精品一卡二卡三卡四卡无卡| 亚洲成av人片在线观看无| 久久网站热最新地址| 在线碰免费视频在线观看| 日韩人妻无码一区二区三区| 成人同人动漫免费观看| 国产精品久久久久久69| 久久久婷婷一区二区三区不卡| 一区二区三区影视| 免费男女羞羞的视频网站中文字幕| 欧美e片成人在线播放乱妇| 一本一本久久a久久精品综合小说| 成人国产精品一区二区网站| 青青在线免费观看视频| 91精品人妻一区二区三区| 亚洲国产精品中文| 99er在线视频| 日本一卡2卡三卡4卡网站| 日韩理论片在线观看| 五月天婷婷社区| 日本黄在线观看| 91亚洲国产成人久久精品| 欧美激情网站在线观看| 在线视频毛片| 亚洲国产一区二区视频| 91丨国产丨九色丨pron| 美丽的小蜜桃4春潮| 这里只有精品丝袜| 黄色国产在线视频| 蜜桃无码一区二区三区| 国产三级欧美三级|