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)步
...