十一 組合和繼承
OOD(Object Oriented Design)
(1)基于對象:單一類的寫法;
(2)面向?qū)ο螅侯惻c類之間的關(guān)系,包含三種:Composition(復(fù)合)党窜、Delegation(委托)、 Inheritance(繼承)谷朝。
11.1 Composition表示has-a
如上圖所示激捏,queue類中有deque類,稱為queue has-a deque即類的復(fù)合煮盼。
這也是一種設(shè)計模式Adapter短纵,queue類擁有deque類,而且queue所有的函數(shù)功能都借用deque類來實現(xiàn)僵控。deque功能非常全香到,queue根據(jù)用戶開放部分的功能,queue是一種adapter。
這是復(fù)合在內(nèi)存中的表現(xiàn)悠就,queue類有deque類千绪,deque類有Itr類,sizeof的計算如圖所示:
類之間在復(fù)合關(guān)系下構(gòu)造函數(shù)與析構(gòu)函數(shù)的順序為:
(1)構(gòu)造由內(nèi)而外梗脾。container的構(gòu)造函數(shù)首先調(diào)用Component的default構(gòu)造函數(shù)荸型,然后才執(zhí)行自己。
Container::Container(...):Component( ){ ... };
(2) 析構(gòu)由外而內(nèi)炸茧。container的析構(gòu)函數(shù)首先執(zhí)行自己瑞妇,然后才調(diào)用component的析構(gòu)函數(shù)。
Container::~Container(...){ ... ~Component( ) };
11.2 Delegation(委托) (Composition by reference)
(1)如圖所示左邊有指向右邊的指針梭冠,這種叫委托辕狰。它和復(fù)合的區(qū)別是復(fù)合內(nèi)外部是一起出現(xiàn),兩者生命周期同步控漠;而委托則是允許外部先創(chuàng)建出來柳琢,等需要的時候再把內(nèi)部寫好,兩者生命周期不同步润脸。
(2) 圖示寫法叫做Handle/Body或者pointer implementation(pimpl)柬脸。左邊是對外的接口,右邊是功能的實現(xiàn)毙驯,右邊不影響左邊倒堕,右邊可以更改甚至指向別的類,具有良好的彈性爆价。
11.3 Inheritance(繼承)表示is-a
子類從父類繼承了數(shù)據(jù)和函數(shù)(函數(shù)繼承的是調(diào)用權(quán))垦巴。
類之間在繼承關(guān)系下構(gòu)造函數(shù)與析構(gòu)函數(shù)的順序為:
(1)構(gòu)造由內(nèi)而外。Derived的構(gòu)造函數(shù)首先調(diào)用Base的default構(gòu)造函數(shù)铭段,然后才執(zhí)行自己骤宣。
Derived::Derived(...):Base( ){ ... };
(2) 析構(gòu)由外而內(nèi)。Derived的析構(gòu)函數(shù)首先執(zhí)行自己序愚,然后才調(diào)用Base的析構(gòu)函數(shù)憔披。Base class的析構(gòu)必須是virtual,否則會出現(xiàn)undefined behavior爸吮!
Derived::~Derived(...){ ... ~Base( ) };
十二 虛函數(shù)與多態(tài)
12.1 虛函數(shù)及其應(yīng)用
繼承最有價值的是和虛函數(shù)的搭配使用芬膝。成員函數(shù)從虛函數(shù)的角度出發(fā)分為三種:
(1)non-virtual函數(shù):你不希望derived class重新定義(override,重寫)它形娇;
(2)virtual函數(shù):你希望derived class重新定義(override锰霜,重寫)它,且你對它已有默認定義桐早;
(3)pure virtual函數(shù):你希望derived class一定要重新定義(override癣缅,重寫)它厨剪,且你對它沒有默認定義。
虛函數(shù)應(yīng)用實例如下:
舊文件的開啟與讀取友存,check file name 祷膳、search file 、open file任何人寫的都差不多爬立,可以事先寫钾唬,但是因為每個人讀取的文件類型不一樣万哪,所以讀取這個動作不能事先寫侠驯。框架如下:
應(yīng)用框架CDocument中已經(jīng)事先寫好check file name 奕巍、search file等函數(shù)吟策,CMyDoc是CDocument的子類,沒法事先寫的Serialize()設(shè)計為虛函數(shù)(可能為空函數(shù)的止,也可能為純虛函數(shù))檩坚,使用流程如圖箭頭所示,創(chuàng)建一個子類的對象诅福,通過子類的對象調(diào)用父類的函數(shù)匾委,遇到Serialize()時父類函數(shù)去找子類的定義,然后再將剩余的動作完成氓润,將Serialize()延緩到子類去定義赂乐,這種用法就叫做Template Method(23種設(shè)計模式之一,Method是java中的函數(shù))咖气。
這種設(shè)計模式適合做框架挨措,MFC大量用到Template Method。
具體代碼實現(xiàn)(仿真):
12.2 繼承+復(fù)合關(guān)系下的構(gòu)造與析構(gòu)
在圖示繼承+復(fù)合關(guān)系下構(gòu)造函數(shù)與析構(gòu)函數(shù)的順序為:
(1)構(gòu)造由內(nèi)而外崩溪。Derived的構(gòu)造函數(shù)首先調(diào)用Base的default構(gòu)造函數(shù)浅役,然后調(diào)用Component的構(gòu)造函數(shù),最后才執(zhí)行自己伶唯。
Derived::Derived(...):Base( )觉既,Component(){ ... };
(2) 析構(gòu)由外而內(nèi)。Derived的析構(gòu)函數(shù)首先執(zhí)行自己乳幸,然后調(diào)用Component的析構(gòu)函數(shù)奋救,最后調(diào)用Base的析構(gòu)函數(shù)。
Derived::~Derived(...){ ... ~Component()反惕,~Base( ) };
在圖示繼承+復(fù)合關(guān)系下構(gòu)造函數(shù)與析構(gòu)函數(shù)的順序為:
(1)構(gòu)造由內(nèi)而外尝艘。Derived的構(gòu)造函數(shù)首先調(diào)用Component的default構(gòu)造函數(shù),然后調(diào)用Base的構(gòu)造函數(shù)姿染,最后才執(zhí)行自己背亥。
Derived::Derived(...):Component()秒际,Base( ){ ... };
(2) 析構(gòu)由外而內(nèi)。Derived的析構(gòu)函數(shù)首先執(zhí)行自己狡汉,然后調(diào)用Base的析構(gòu)函數(shù)娄徊,最后調(diào)用Component的的析構(gòu)函數(shù)。
Derived::~Derived(...){ ... ~Base( ) 盾戴,~Component()};
12.3 多態(tài)實例
功能最強大的是委托+繼承設(shè)計方式寄锐。
委托+繼承設(shè)計實例1(Observer):
左邊為放數(shù)據(jù)的class,右邊為觀察的class尖啡,左邊可以有很多的右邊橄仆,左邊數(shù)據(jù)裝有指向右邊指針的容器,右邊可以被繼承衅斩,將來創(chuàng)建的子類is a Observer都可以放在容器里面盆顾,所以可以產(chǎn)生不同的Observer。
左邊要提供注冊和注銷的功能畏梆,如圖attach傳入Observer放到容器里頭您宪。左邊還應(yīng)該有notify把所有Observer進行遍歷,去通知Observer奠涌,內(nèi)容由Observer寫好宪巨,左邊調(diào)用。
如圖所示有多個窗口看同一份文件或者不同角度看同一份數(shù)據(jù)溜畅。實現(xiàn)代碼如下:
委托+繼承設(shè)計實例2(Composite):
文件系統(tǒng):Primitive代表文件捏卓,Composite是一個容器,容納很多個Primitive和Composite达皿,所以設(shè)計一個父類Component天吓,Primitive和Composite都is a Component,Component和Composite是委托的關(guān)系峦椰。add函數(shù)和容器類似龄寞,add不能設(shè)計為純虛函數(shù),因為Primitive不能定義汤功。
委托+繼承設(shè)計實例3(Prototype):
我需要一個樹狀繼承體系物邑,子類未來才被派生,不知道未來子類的名稱滔金。讓派生的子類創(chuàng)建一個自己當(dāng)成原型Prototype色解,讓我有辦法去看到子類創(chuàng)建出來的的原型放在什么位置上。
在LandSatImage類里面放一個靜態(tài)的對象LAST餐茵,他的類型是LandSatImage(自己)科阎,構(gòu)造函數(shù)寫成私有的,通過私有的構(gòu)造函數(shù)調(diào)用addPrototype忿族,將自己掛上去锣笨,addPrototype是父類寫的蝌矛,它將得到的指針放到容器里頭去,這樣就可以使破折號下面創(chuàng)建的原型放到上面去错英,可以被上面看到入撒。子類準(zhǔn)備一個函數(shù)clone,它new一個自己椭岩。破折號以上可以通過原型(這是一個對象)可以調(diào)用clone這個函數(shù)茅逮,做出一個副本,如果沒有原型則不能判哥。
clone不能是靜態(tài)函數(shù)献雅,因為靜態(tài)函數(shù)的調(diào)用需要class name,這里沒有。
實現(xiàn)代碼如下: