11.組合與繼承
. 遇到復雜問題時鸯隅,需要類與類相關聯(lián)澜建,即面向對象思想
Composition復合
. 表示has-a, (里面有個類)
. 復合類似一種類與類的包含關系
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是兩端可進出,queue是末端今前端出fifo
? ? void push (const value_type& x) { c.push_back(x) ; }
? ? void pop() {c.pop_front() ; }
} ; ? ? ? ?//這時候所有的功能deque都可以完成蝌以,則deque借用后炕舵,不需要自己寫新功能了
. 圖示時,用實心菱形加箭頭◆→表示跟畅,箭頭指向的一端為被包涵的類咽筋,實心菱形一端為容器
. 這時候,容器可以借用另外函數(shù)的功能實現(xiàn)自己的功能
. 復合可以將其他類函數(shù)改裝成為自己需要的函數(shù)徊件,即可看作一種Adapter
. 復合可以嵌套
. 從內(nèi)存角度:復合所占大小Sizeof要把所包涵的類一層一層計算進去
Composition復合關系下的構造和析構
. Container◆→Component
. 構造時奸攻,由內(nèi)而外,Container的構造函數(shù)先調用Component的構造函數(shù)虱痕,再執(zhí)行自己
Container::Container(...) : Component() {...} ; ? ? ? ? ? ? //調用的是Component的默認構造函數(shù)
. 析構時睹耐,由外而內(nèi),先執(zhí)行自己部翘,再調用Component的析構函數(shù)
Container :: ~Container(...) { ... ~Conponent() } ;? ? ??
. 編譯器會幫忙調用Component構造和析構函數(shù)硝训,但只能調用默認的
. 如果不想調用默認構造函數(shù),需要自己寫Component構造函數(shù)的調用
Delegation委托. Composition by reference
. 如果是有指針的類,而指針指向另一個類
. 圖示時窖梁,用空心菱形加箭頭◇→表示赘风,箭頭指向的一端為被類包涵的指針所指向的類
class StringRep ;
class String
{
public:
? ? String() ;
? ? String(const char* s) ;
? ? String(const String& s) ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//拷貝構造
? ? String &operator = (const String& s) ; ? ? ? ? ? ? ?//拷貝賦值
? ? ~String() ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //析構
...
Private :
? ? StringRep* rep ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//pimpl
} ;
. 以指針為方式調用另一個類,即為Delegation窄绒,也可成為Composition by reference
. 當用指針相連時贝次,數(shù)據(jù)生命不一致,與Composition相區(qū)別
. →一端當需要時才去創(chuàng)建彰导,◇一端只是對外的接口蛔翅,當需要動作時都調用→一端的類去實現(xiàn)
. 這種寫法叫做pimpl(pointer to implimentation),又叫做Handle/Body
. 當這樣寫類時位谋,前面接口可以保持不變山析,指針后面真正實現(xiàn)的類可以切換,不會影響客戶端
. 又叫做編譯防火墻
. reference counting掏父,例如當有三個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繼承關系下的構造和析構
. Derived-?Base
. 由于是也是包含關系,所以與Component類似
. 構造由內(nèi)而外苫费,Derived構造函數(shù)先調用Base的默認構造函數(shù)汤锨,然后再執(zhí)行自己
Derived :: Derived(...) : Base() {...};
. 析構由外而內(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,復寫)
. virtual函數(shù)铐维,希望derived class重新定義柬泽,但它自己有默認定義
. pure virtual函數(shù),希望derived class一定要重新定義方椎,它自己沒有默認定義
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ù)
. 很多時候在不同軟件中钧嘶,都有某功能相類似棠众,例如文件編輯的軟件中的打開功能,這時候寫一個父類來解決相同操作步驟,將特殊部分列為虛函數(shù)闸拿,以此來提高效率
. 父類可能很久前就寫好的空盼,實際運行main時通過子類對象調用父類函數(shù)
CDocument::OnFileOpen() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //(1)
{
...
? ? Serialize() ? ? ? ? ? ?//函數(shù)中做了一些固定動作把其中的一些關鍵部分延緩到子類去給定,以后由子類寫出
...
}
? ? class CMyDoc :public CDocument ? ? ? ? ? ? ? ? ? ? //(2)
{
virtual Serialize() {...}
} ;
main()
{
? ? CMyDoc myDoc ;
...
? ? myDoc.OnFileOpen() ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //CDocument::OnFileOpen(&myDoc);
} ? ? ? ? ? ? ?//調用順序新荤,通過(2)進入到(1)開始調用揽趾,到S函數(shù)時,調用(2)中virtual苛骨,再回(1)繼續(xù)
. 通過子類調用父類函數(shù)
. 父類中的關鍵動作可延緩到子類去操作篱瞎,叫做Template Method (不是指的模板)
. 在框架中,會設計出同類固定功能痒芝,將無法決定的功能留為虛函數(shù)留給子類去定義
. MFC就是一種Template Method
. 在上面栗子中俐筋,調用Serialize時,編譯器在做這樣的動作:
this->Serialize() ;
(*(this->vptr)[n])(this) ;
Inheritance+Composition關系下的構造和析構
. Derived既含父類又含Component時严衬,
. Derived含父類澄者,其父類又含Component時,一層一層構造和析構即可
Delegation+Inheritance 委托+繼承
. 委托+繼承的功能最強大
class Observer
{
public :
? ? virtual void update(Subject* sub请琳,int value)=0 ; ? ? ? ? //將來可以派生不同的子類
} ;
class Subject ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//需要很多觀察器粱挡,與Observer是委托關系
{
? ? int m_value ;
? ? vector<Observer*>m_views ; ? ? ? ? ? ? ? //準備一個容器,里面可以放好多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.委托相關設計
. 若要寫一個file system或者window system询筏,先要理清需要構造的層次關系,再考慮需要那些class和關系
Composite:以file system為例:
. 先要準備一個primitive嘀倒,也可稱為file
. 另外要準備一個Composite屈留,一個容器,可以容納很多file测蘑,也可以放很多他自己
. Composite還需要一個可以添加Primitive也可以添加他自己的一個函數(shù)
. 這時候需要寫一個Primitive和Composite共同的父類灌危,即Component
. Component中可以寫一個添加函數(shù),這時候Composite就可以委托他實現(xiàn)添加功能
. 這種方法即為設計模式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)對象構造時馆衔,需要調用構造函數(shù)瘟判,做一個private數(shù)據(jù)(圖示為前加負號,protected圖示為前加#)
. 這時構造函數(shù)只能被自己調用角溃,這個構造函數(shù)需要調用父類添加原型函數(shù)把自己登記到父類框架端
. 父類中添加原型功能可以把得到的原型放入容器
. 子類還需要自己準備一個clone函數(shù)拷获,用來new自己,這時候通過原型對象可以調用clone
. 所有的子類都需要這樣來創(chuàng)建
. 因為靜態(tài)函數(shù)的調用需要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外進行一次定義
Image *Image::_prototypes[];
int Image::_nextSlot;
Image *Image::findAndClone(imageType type) ? ? ? //客戶需要Image對象實例時候調用這個公開靜態(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自己 未蝌,調用第二構造驮吱,參數(shù)任意
protected :
? ? LandSatImage(int dummy){_id = _count++; } ? ? ? ? ? ? //第二個構造函數(shù),用來給clone調用的構造函數(shù)
private :
? ? static LandSatImage _LandSatImage ? ? ? ? ? ? ? ? ? ? ? //創(chuàng)建靜態(tài)原型
? ? LandSatImage(){addPrototype(this) ; } ? ? ? ? ? ? ? ? ? ? //讓原型調用父類添加函數(shù)登記到父類端的構造函數(shù)
? ? int _id ;
? ? static int _count ;
} ;
LandSatImage LandSatImage::_landSatImage ;
int LandSatImage::_count = 1 ;
...
在各種設計模式中有很多抽象思考需要構思萧吠,在不斷寫代碼中進行進步
...