GeekBand極客班C++面向?qū)ο蟾呒壘幊蹋ㄉ希┑谌芄P記

11.組合與繼承

. 遇到復(fù)雜問題時,需要類與類相關(guān)聯(lián),即面向?qū)ο笏枷?br>

Composition復(fù)合

. 表示has-a, (里面有個類)

. 復(fù)合類似一種類與類的包含關(guān)系


template <class T, class Sequence = deque(T)>

class queue

{

...

protected : ? ? ? ? ? ? ? ? ? ? ? ? //給子類提供接口

? ? Sequence c; ? ? ? ? ? ? ? ? //底層容器

public : ? ? ? ? ? ? ? ? ? ? ? ? ? ? //以下操作函數(shù)完全利用c的操作函數(shù)完成

? ? bool empty() const {return c.empty() ; }

? ? size_type size() const {return c.size() ; }

? ? reference front() {return c.front() ;}

? ? reference back() {return c.back() ; } ? ? ? ?//deque是兩端可進(jìn)出,queue是末端今前端出fifo

? ? void push (const value_type& x) { c.push_back(x) ; }

? ? void pop() {c.pop_front() ; }

} ; ? ? ? ?//這時候所有的功能deque都可以完成,則deque借用后,不需要自己寫新功能了


. 圖示時,用實心菱形加箭頭◆→表示,箭頭指向的一端為被包涵的類,實心菱形一端為容器

. 這時候,容器可以借用另外函數(shù)的功能實現(xiàn)自己的功能

. 復(fù)合可以將其他類函數(shù)改裝成為自己需要的函數(shù),即可看作一種Adapter

. 復(fù)合可以嵌套

. 從內(nèi)存角度:復(fù)合所占大小Sizeof要把所包涵的類一層一層計算進(jìn)去

Composition復(fù)合關(guān)系下的構(gòu)造和析構(gòu)

. Container◆→Component

. 構(gòu)造時,由內(nèi)而外,Container的構(gòu)造函數(shù)先調(diào)用Component的構(gòu)造函數(shù),再執(zhí)行自己


Container::Container(...) : Component() {...} ; ? ? ? ? ? ? //調(diào)用的是Component的默認(rèn)構(gòu)造函數(shù)


. 析構(gòu)時,由外而內(nèi),先執(zhí)行自己,再調(diào)用Component的析構(gòu)函數(shù)


Container :: ~Container(...) { ... ~Conponent() } ;? ? ??


. 編譯器會幫忙調(diào)用Component構(gòu)造和析構(gòu)函數(shù),但只能調(diào)用默認(rèn)的

. 如果不想調(diào)用默認(rèn)構(gòu)造函數(shù),需要自己寫Component構(gòu)造函數(shù)的調(diào)用

Delegation委托. Composition by reference

. 如果是有指針的類,而指針指向另一個類

. 圖示時,用空心菱形加箭頭◇→表示,箭頭指向的一端為被類包涵的指針?biāo)赶虻念?/p>


class StringRep ;

class String

{

public:

? ? String() ;

? ? String(const char* s) ;

? ? String(const String& s) ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//拷貝構(gòu)造

? ? String &operator = (const String& s) ; ? ? ? ? ? ? ?//拷貝賦值

? ? ~String() ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //析構(gòu)

...

Private :

? ? StringRep* rep ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//pimpl

} ;


. 以指針為方式調(diào)用另一個類,即為Delegation,也可成為Composition by reference

. 當(dāng)用指針相連時,數(shù)據(jù)生命不一致,與Composition相區(qū)別

. →一端當(dāng)需要時才去創(chuàng)建,◇一端只是對外的接口,當(dāng)需要動作時都調(diào)用→一端的類去實現(xiàn)

. 這種寫法叫做pimpl(pointer to implimentation),又叫做Handle/Body

. 當(dāng)這樣寫類時,前面接口可以保持不變,指針后面真正實現(xiàn)的類可以切換,不會影響客戶端

. 又叫做編譯防火墻

. reference counting,例如當(dāng)有三個object共享同一個rep(改變內(nèi)容互不影響copy on write)

Inheritance繼承,表示is-a


struct _List_node_base ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//以struct為例子

{

? ? _List_node_base* _M_next ;

? ? _List_node_base* _M_prev ;

} ;

template<typename _Tp>

struct _List_node ?: public _List_node_base ? ? ? ? //子類,將struct從父類繼承過來

{

? ? _Tp _M_data ;

} ;


. 圖示時,用空心三角形加直線表示?-,橫線一端表示子類,?一端表示父類

. 繼承方式有三種,public、protected、private

.. public繼承可以被任意實體訪問

.. protected繼承只允許子類及本類的成員函數(shù)訪問

.. private繼承只允許本類的成員函數(shù)訪問

. 從內(nèi)存角度,父類數(shù)據(jù)被完整繼承下來到子類,子類對象中包涵父類成分

inheritance繼承關(guān)系下的構(gòu)造和析構(gòu)

. Derived-?Base

. 由于是也是包含關(guān)系,所以與Component類似

. 構(gòu)造由內(nèi)而外,Derived構(gòu)造函數(shù)先調(diào)用Base的默認(rèn)構(gòu)造函數(shù),然后再執(zhí)行自己


Derived :: Derived(...) : Base() {...};


. 析構(gòu)由外而內(nèi)


Derived :: ~Derived(...) {... ~Base() };


. base class的dtor必須是virtual,否則會出現(xiàn)undefined behaviour

. 也由編譯器自動完成

12.虛函數(shù)與多態(tài)

Inheritance with virtual functions 帶虛函數(shù)的繼承

. 語法形式,函數(shù)前加virtual

. non-virtual函數(shù),不希望derived class派生類(子類)重新定義(override,復(fù)寫)

. virtual函數(shù),希望derived class重新定義,但它自己有默認(rèn)定義

. pure virtual函數(shù),希望derived class一定要重新定義,它自己沒有默認(rèn)定義


class Shape

{

public :

? ? virtual void draw() const = 0 ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//pure virtual

? ? virtual void error(const std :: string& msg) ; ? ? ? ? ? ? ? ? ?//impure virtual

? ? int objectID() const ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //non-virtual

...

}

class Rectangle : publicShape{...} ;


. 有時候純虛函數(shù)也可以有定義

. 在類中考慮到繼承問題時,要考慮搭配虛函數(shù)

. 很多時候在不同軟件中,都有某功能相類似,例如文件編輯的軟件中的打開功能,這時候?qū)懸粋€父類來解決相同操作步驟,將特殊部分列為虛函數(shù),以此來提高效率

. 父類可能很久前就寫好的,實際運行main時通過子類對象調(diào)用父類函數(shù)


CDocument::OnFileOpen() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //(1)

{

...

? ? Serialize() ? ? ? ? ? ?//函數(shù)中做了一些固定動作把其中的一些關(guān)鍵部分延緩到子類去給定,以后由子類寫出

...

}

? ? class CMyDoc :public CDocument ? ? ? ? ? ? ? ? ? ? //(2)

{

virtual Serialize() {...}

} ;

main()

{

? ? CMyDoc myDoc ;

...

? ? myDoc.OnFileOpen() ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //CDocument::OnFileOpen(&myDoc);

} ? ? ? ? ? ? ?//調(diào)用順序,通過(2)進(jìn)入到(1)開始調(diào)用,到S函數(shù)時,調(diào)用(2)中virtual,再回(1)繼續(xù)


. 通過子類調(diào)用父類函數(shù)

. 父類中的關(guān)鍵動作可延緩到子類去操作,叫做Template Method (不是指的模板)

. 在框架中,會設(shè)計出同類固定功能,將無法決定的功能留為虛函數(shù)留給子類去定義

. MFC就是一種Template Method

. 在上面栗子中,調(diào)用Serialize時,編譯器在做這樣的動作:


this->Serialize() ;

(*(this->vptr)[n])(this) ;


Inheritance+Composition關(guān)系下的構(gòu)造和析構(gòu)

. Derived既含父類又含Component時,

. Derived含父類,其父類又含Component時,一層一層構(gòu)造和析構(gòu)即可

Delegation+Inheritance 委托+繼承

. 委托+繼承的功能最強大


class Observer

{

public :

? ? virtual void update(Subject* sub,int value)=0 ; ? ? ? ? //將來可以派生不同的子類

} ;

class Subject ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//需要很多觀察器,與Observer是委托關(guān)系

{

? ? int m_value ;

? ? vector<Observer*>m_views ; ? ? ? ? ? ? ? //準(zhǔn)備一個容器,里面可以放好多Observer的指針

public :

? ? void attach(Observer* obs) ? ? ? ? ? ? ? ? ?//提供注冊功能(還要有注銷功能,栗子沒給出)

? ? { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //附著一個Observer

? ? ? ? m_views.push_back(obs) ; ? ? ? ? ? ? //放到容器中

? ? }

? ? void set_val(int value) ? ? ? ? ? ? ? ? ? ? ? ? //

? ? {

? ? ? ? m_value+value;

? ? ? ? notify() ;

? ? }

? ? void notify()? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //遍歷并通知所有Observer更新信息

? ? {

? ? ? ? for(int i=0;i<m_views.size();i++)

? ? ? ? m_views[i]->update(this,m_value);

? ? }

}


13.委托相關(guān)設(shè)計

. 若要寫一個file system或者window system,先要理清需要構(gòu)造的層次關(guān)系,再考慮需要那些class和關(guān)系

Composite:以file system為例:

. 先要準(zhǔn)備一個primitive,也可稱為file

. 另外要準(zhǔn)備一個Composite,一個容器,可以容納很多file,也可以放很多他自己

. Composite還需要一個可以添加Primitive也可以添加他自己的一個函數(shù)

. 這時候需要寫一個Primitive和Composite共同的父類,即Component

. Component中可以寫一個添加函數(shù),這時候Composite就可以委托他實現(xiàn)添加功能

. 這種方法即為設(shè)計模式Composite,是一個委托+繼承模式

. 代碼如下


class Component

{

? ? int value ;

public :

? ? Component(int val){value=val;}

? ? virtual void add(Component*){} ? ? ? ? ?//需要讓Composite重新定義add功能,所以寫為虛函數(shù)

} ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //但不能是純虛函數(shù),因為Primitive不能有動作

class Composite :public Component ? ?

{

? ? vector<Component*>c; ? ? ? ? ? ? ? ? ? ? //做一個容器存放Component

public :

? ? Composite(int val): Component(val){}

? ? void add(Component* elem) {c.push_back(elem) ; }

...

}

class Primitive :publicComponent

{

public :

? ? Primitive(int val) :Component(val){}

} ;


Prototype

. 框架被建好的時候,因為定義需要被子類來定義,這時候不能new,需要new的class name被還沒創(chuàng)建

. 這時使派生的子類都可以new一個自己作為Prototype,讓框架可以看到Prototype的位置來接收它

. 創(chuàng)建子類時,安排一個靜態(tài)對象(圖示為加下劃線)作為原型,這個原型必須登記到父類框架端

.. 寫代碼時候線寫typename,再寫objectname

. 父類要留有空間來給子類登記原型

. 靜態(tài)對象構(gòu)造時,需要調(diào)用構(gòu)造函數(shù),做一個private數(shù)據(jù)(圖示為前加負(fù)號,protected圖示為前加#)

. 這時構(gòu)造函數(shù)只能被自己調(diào)用,這個構(gòu)造函數(shù)需要調(diào)用父類添加原型函數(shù)把自己登記到父類框架端

. 父類中添加原型功能可以把得到的原型放入容器

. 子類還需要自己準(zhǔn)備一個clone函數(shù),用來new自己,這時候通過原型對象可以調(diào)用clone

. 所有的子類都需要這樣來創(chuàng)建

. 因為靜態(tài)函數(shù)的調(diào)用需要classname,所以需要這樣做

. 代碼如下


#include<iostream>

enum imageType{LAST , SPOT};

class Image

{

public :

? ? virtual void draw()=0 ;

? ? static Image *findAndClone(imageType) ;

protected :

? ? virtual imageType returnType()=0 ;? ? ? ? ? ? ? ? ? ? ? //純虛函數(shù),一定要子類來寫

? ? virtual Image *clone()=0 ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

? ? static void addPrototype(Image *image)? ? ? ? ? ? //子類聲明后,會將他的原型登記過來

? ? {_prototypes[_nextSlot++]=image;}

private : ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//把添加功能登記的每個原型保存到這里

? ? static Image *_prototypes[10]; ? ? ? ? ? ? ? ? ? ? ? ? ? //這個數(shù)組是自己用來存放原型的容器

? ? static int _nextSlot;

} ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//class中靜態(tài)的data必須在class外進(jìn)行一次定義

Image *Image::_prototypes[];

int Image::_nextSlot;

Image *Image::findAndClone(imageType type) ? ? ? //客戶需要Image對象實例時候調(diào)用這個公開靜態(tài)函數(shù)

{

? ? for(int i=0;i<_nextSlot;i++) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//在容器中尋找需要的class來clone出來

? ? ? ? if(_prototypes[i]->returnType()==type)

? ? ? ? ? ? return _prototypes[i]->clone();

}


class LandSatImage :public Image ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//繼承父類

{

public :

? ? imageType returnType(){return LAST ;}

? ? void draw(){cout<<"LandSatImage::draw"<<_id<<endl ; }?

? ? Image *clone(){return new LandSatImage(1) ; } ? ? ? ? ? ? ? ? ?// 用來new自己 ,調(diào)用第二構(gòu)造,參數(shù)任意

protected :

? ? LandSatImage(int dummy){_id = _count++; } ? ? ? ? ? ? //第二個構(gòu)造函數(shù),用來給clone調(diào)用的構(gòu)造函數(shù)

private :

? ? static LandSatImage _LandSatImage ? ? ? ? ? ? ? ? ? ? ? //創(chuàng)建靜態(tài)原型

? ? LandSatImage(){addPrototype(this) ; } ? ? ? ? ? ? ? ? ? ? //讓原型調(diào)用父類添加函數(shù)登記到父類端的構(gòu)造函數(shù)

? ? int _id ;

? ? static int _count ;

} ;

LandSatImage LandSatImage::_landSatImage ;

int LandSatImage::_count = 1 ;


...

在各種設(shè)計模式中有很多抽象思考需要構(gòu)思,在不斷寫代碼中進(jìn)行進(jìn)步

...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容