GeekBand C++ 第三周

11. 組合與繼承

Object Oriented Programming,Object Oriented Design,OOP,OOD

1. Composition(復(fù)合),表示has-a

Composition

實(shí)心菱形+箭頭 Class A 指向 Class B,則Class A has-a Class B,即為復(fù)合關(guān)系。

Class A has-a Class B,但是Class A沒有添加任何新的功能,只是開放了部分Class B的功能,這種情況表現(xiàn)出了Adapter設(shè)計(jì)模式。

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

構(gòu)造由內(nèi)而外
??Container的構(gòu)造函數(shù)首先調(diào)用Component的default構(gòu)造函數(shù),然后才執(zhí)行自己的。當(dāng)需要編譯器自己生成時(shí),則需要調(diào)用default構(gòu)造函數(shù),如果不需要調(diào)用default構(gòu)造函數(shù),則需要自己在initialization list寫明。

Container::Container(...): Component(){...};

析構(gòu)由外而內(nèi)
??Container的析構(gòu)函數(shù)首先執(zhí)行自己的,然后才調(diào)用Component的default析構(gòu)函數(shù)。

Container::~Container(...){ ... ~Component() };

2. Delegation(委托),Composition by reference。

Delegation

空心菱形+箭頭 Class A 指向 Class B,則Class A Delegation Class B。

Class A 中有一個(gè) Class B 的 pointer。任何時(shí)候,A可以調(diào)用B的方法完成任務(wù)。更恰當(dāng)?shù)谋硎龇椒ㄊ?composition by reference,它和composition的區(qū)別:

當(dāng)Class A 和Class B 是composition關(guān)系時(shí),有了A,則同時(shí)有了B,生命周期同步。而delegation,也許先有了A,此時(shí)僅僅有了B的指針,當(dāng)需要時(shí),才去創(chuàng)建B,生命周期不同步。

在delegation下,Class A 僅僅是對外的接口,而真正的實(shí)現(xiàn)都放在Class B中,這種寫法稱作pimpl(point to implantation)也稱為Handle/Body。

Class A內(nèi)部的指針,不僅僅局限于指向Class B,而且當(dāng)B需要變動(dòng)時(shí),Class A不用重新編譯,僅僅需要重新編譯Class B,所以此種寫法又稱為編譯防火墻。

3. Inheritance(繼承),表示is-a

Inheritance

直線+空心三角形,由子類指向父類,表示inheritance關(guān)系。
??繼承關(guān)系有三種,public,protect,private。其中public繼承,表示is-a的關(guān)系。

struct _List_node_base{
    _List_node_base* _M_next;
    _list_node_base* _M_prev;
}:
typeplate<typename _Tp>
struct _List_node
    : public _List_node_base{
    _Tp _M_data;    
};

上段代碼使用了public繼承,因?yàn)橹挥袛?shù)據(jù),沒有方法,表示子類繼承了父類的數(shù)據(jù)。

Inheritance關(guān)系下的構(gòu)造和析構(gòu)
??Class Derived 繼承 Class Base,此時(shí)我們稱Derived object 中含有 Base Part。

構(gòu)造由內(nèi)而外
??Derived的構(gòu)造函數(shù)首先調(diào)用Base的default構(gòu)造函數(shù),然后才執(zhí)行自己。

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

析構(gòu)由外而內(nèi)
??Derived的析構(gòu)函數(shù)首先執(zhí)行自己,然后才調(diào)用Base的default析構(gòu)函數(shù)。

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

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

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

1. Inheritance(繼承) with virtual functions(虛函數(shù))

virtual functions分為三種:

  • non-virtual函數(shù):你不希望derived class重新定義(override,復(fù)寫)它。
  • virtual函數(shù):你希望derived class重新定義(override,復(fù)寫)它,且你對它已經(jīng)有默認(rèn)定義。
  • pure virtual函數(shù):你希望derived class一定要重新定義(override,復(fù)寫)它,你對它沒有默認(rèn)定義。
class Shape{
public:
    virtual void draw() const = 0;//pure virtual
    virtual void error(cosnt std::string& msg);//impure virtual
    int objectID() cosnt;//non-virtual
};

class Rectangle: public Shape{...};
class Ellipse: public Shape{...};

2. Template Method

Template Method

父類CDocument中的OnFileOpen()函數(shù)無法確定Serialize()中的具體行為,因此將它聲明為虛函數(shù),由子類去完成,在實(shí)際的OnFileOpen()函數(shù)執(zhí)行過程中,調(diào)用的Serialize()也完全取決于子類。則對于父類CDocument中的OnFileOpen()函數(shù),是一種在Application framework,此種寫法被稱為Template Method。

對于虛函數(shù)Serialize()的調(diào)用,真正的this指針是CMyDoc類型的,當(dāng)調(diào)用時(shí),也同樣是通過this指針來調(diào)用,因此當(dāng)調(diào)用虛函數(shù)Serialize()時(shí),調(diào)用的是CMyDoc的Serialize(),而非父類的Serialize()。

#include<iostream>
using namespace std;

class CDocument{
public:
    void OnFileOpen(){
        //這是個(gè)算法,每個(gè)cout輸出代表一個(gè)實(shí)際動(dòng)作
        cout << "dialog..." << endl;
        cout << "check file status..." << endl;
        cout << "open file..." << endl;
        Serialize();
        cout << "close file..." << endl;
        cout << "..." << endl;
    }
    
    virtual void Serialize(){ };
};

class CMyDoc : public CDocument{
public:
    virtual void Serialize(){
        //只有應(yīng)用程序本身才知道如何讀取自己的文件(格式)
        cout << "CMyDoc::Serialize()" << endl;
    }
};

int main(){
    CMyDoc myDoc;
    myDoc.OnFileOpen();
    return 0;
}

輸出結(jié)果:

dialog...
check file status...
open file...
CMyDoc::Serialize()
close file...
...

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

第一種情況:

#include<iostream>
using namespace std;

class Component{
public:
    Component(){ cout << "Component ctor called" << endl; }
    ~Component(){ cout << "Component dtor called" << endl; }
};

class Base{
public:
    Base(){ cout << "Base ctor called" << endl; }
    virtual ~Base(){ cout << "Base dtor called" << endl; }
};

class Derived : public Base{
public:
    Derived(){ cout << "Derived ctor called" << endl; }
    ~Derived(){ cout << "Derived dtor called" << endl; }
private:
    Component component;
};

int main(){
    Derived *pDerived = new Derived;
    delete pDerived;
    getchar();
    return 0;
}

運(yùn)行結(jié)果:

Base ctor called
Component ctor called
Derived ctor called
Derived dtor called
Component dtor called
Base dtor called

由運(yùn)行結(jié)果可以看出:

  • 構(gòu)造由內(nèi)而外,Derived首先調(diào)用了Base的默認(rèn)構(gòu)造函數(shù),然后再調(diào)用Component的默認(rèn)構(gòu)造函數(shù),最后調(diào)用自己的。
  • 析構(gòu)由內(nèi)而外,Derived首先調(diào)用自己的析構(gòu)函數(shù),然后再調(diào)用Component的析構(gòu)函數(shù),最后調(diào)用Base的析構(gòu)函數(shù)。

第二種情況:

#include<iostream>
using namespace std;

class Component{
public:
    Component(){ cout << "Component ctor called" << endl; }
    ~Component(){ cout << "Component dtor called" << endl; }
};

class Base{
public:
    Base(){ cout << "Base ctor called" << endl; }
    virtual ~Base(){ cout << "Base dtor called" << endl; }
private:
    Component component;
};

class Derived : public Base{
public:
    Derived(){ cout << "Derived ctor called" << endl; }
    ~Derived(){ cout << "Derived dtor called" << endl; }
};

int main(){
    Derived *pDerived = new Derived;
    delete pDerived;
    getchar();
    return 0;
}

運(yùn)行結(jié)果:

Component ctor called
Base ctor called
Derived ctor called
Derived dtor called
Base dtor called
Component dtor called

由運(yùn)行結(jié)果可以看出:

  • 構(gòu)造由內(nèi)而外,Derived首先調(diào)用了Component的默認(rèn)構(gòu)造函數(shù),然后再調(diào)用Base的默認(rèn)構(gòu)造函數(shù),最后調(diào)用自己的。
  • 析構(gòu)由內(nèi)而外,Derived首先調(diào)用自己的析構(gòu)函數(shù),然后再調(diào)用Base的析構(gòu)函數(shù),最后調(diào)用Component的析構(gòu)函數(shù)。

4. Delegation(委托)+Inheritance(繼承)

23個(gè)設(shè)計(jì)模式,最經(jīng)典的面向?qū)ο蟪绦蛟O(shè)計(jì)。

Observer

當(dāng)一個(gè)對象的改變需要同時(shí)改變其他對象的時(shí)候,而且它不知道具體有多少對象有待改變時(shí),應(yīng)該考慮使用觀察者模式。
??觀察者模式所做的工作其實(shí)就是在解除耦合。讓耦合的雙方都依賴于抽象,而不是依賴于具體。從而使得各自的變化都不會影響另一邊的變化。


Observer
  • Subject類,可翻譯為主題或抽象通知者,一般用一個(gè)抽象類或者一個(gè)借口實(shí)現(xiàn)。它把所有對觀察者對象的引用保存在一個(gè)聚集里,每個(gè)主題都可以有任何數(shù)量的觀察者。抽象主題提供一個(gè)借口,可以增加和刪除觀察者對象。

  • Observer類,抽象觀察者,為所有的具體觀察者定義一個(gè)借口,在得到主題的通知時(shí)更新自己。這個(gè)借口叫做更新接口。抽象觀察者一般用一個(gè)抽象類或者一個(gè)接口實(shí)現(xiàn)。更新接口通常包含一個(gè)Update()方法。

  • ConcreteSubject類,叫做具體主題或具體通知者,將有關(guān)狀態(tài)存入具體通知者對象;在具體主題的內(nèi)部狀態(tài)改變時(shí),給所有等級過的觀察者發(fā)出通知。通常用一個(gè)具體子類實(shí)現(xiàn)。

  • ConcreteObserver類,具體觀察者,實(shí)現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。具體觀察者角色可以保存一個(gè)指向一個(gè)具體主題對象的引用。

Composite

組合模式:將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu)。Composite使得用戶對單個(gè)對象和組合對象的使用具有一致性。

有時(shí)候又叫做部分-整體模式,它使我們樹型結(jié)構(gòu)的問題中,模糊了簡單元素和復(fù)雜元素的概念,客戶程序可以向處理簡單元素一樣來處理復(fù)雜元素,從而使得客戶程序與復(fù)雜元素的內(nèi)部結(jié)構(gòu)解耦。

組合模式讓你可以優(yōu)化處理遞歸或分級數(shù)據(jù)結(jié)構(gòu)。有許多關(guān)于分級數(shù)據(jù)結(jié)構(gòu)的例子,使得組合模式非常有用武之地。關(guān)于分級數(shù)據(jù)結(jié)構(gòu)的一個(gè)普遍性的例子是你每次使用電腦時(shí)所遇到的:文件系統(tǒng)。文件系統(tǒng)由目錄和文件組成。每個(gè)目錄都可以裝內(nèi)容。目錄的內(nèi)容可以是文件,也可以是目錄。按照這種方式,計(jì)算機(jī)的文件系統(tǒng)就是以遞歸結(jié)構(gòu)來組織的。如果你想要描述這樣的數(shù)據(jù)結(jié)構(gòu),那么你可以使用組合模式Composite。


Composite
  • 抽象構(gòu)件角色(component):是組合中的對象聲明接口,在適當(dāng)?shù)那闆r下,實(shí)現(xiàn)所有類共有接口的默認(rèn)行為。聲明一個(gè)接口用于訪問和管理Component子部件。這個(gè)接口可 以用來管理所有的子對象。(可選)在遞歸結(jié)構(gòu)中定義一個(gè)接口,用于訪問一個(gè)父部件,并在合適的情況下實(shí)現(xiàn)它。
  • 樹葉構(gòu)件角色(Leaf):在組合樹中表示葉節(jié)點(diǎn)對象,葉節(jié)點(diǎn)沒有子節(jié)點(diǎn)。并在組合中定義圖元對象的行為。
  • 樹枝構(gòu)件角色(Composite):定義有子部件的那些部件的行為。存儲子部件。在Component接口中實(shí)現(xiàn)與子部件有關(guān)的操作。
  • 客戶角色(Client):通過component接口操縱組合部件的對象。

Prototype

當(dāng)有類似這種需求,需要去創(chuàng)建未來的class,但是卻不知道未來具體的calss名稱。那么可以要求派生的子類,自己創(chuàng)建一個(gè)自己,創(chuàng)建出的對象,可以被框架看到的,那么就可以用來當(dāng)一種原型,當(dāng)框架獲得原型之后,可以通過調(diào)用原型的clone函數(shù)來創(chuàng)建對象。

uml圖中表示類的框圖,框中變量帶下劃線的,則為static變量。現(xiàn)在object name,再寫type name。函數(shù)名稱前帶‘+’則為public,‘#’代表protect,‘-’代表private。

類的static變量,一定謹(jǐn)記需要在類外定義,此時(shí)才給變量分配內(nèi)存,所以稱之為定義,類內(nèi)部應(yīng)該是聲明。


Prototype

在上圖中,破折線以下的類,是以后創(chuàng)建的,創(chuàng)建之前,破折線以上的框架是不知道這個(gè)類的名稱,所以也無法創(chuàng)建,但是此時(shí)巧妙的使用了Prototype模式,這個(gè)問題迎刃而解。


Prototype

在Image類的定義中,有兩個(gè)重要的純虛函數(shù)需要子類實(shí)現(xiàn),一個(gè)是returnType函數(shù),用來返回子類的類型,用來區(qū)分不同的子類,另一個(gè)是clone函數(shù),用來讓框架使用原型來創(chuàng)建新的對象。findAndClone函數(shù)則返回該類型新的對象(調(diào)用clone函數(shù))。


Prototype3

在Image的子類定義中,需要兩個(gè)構(gòu)造函數(shù),一個(gè)是給static變量調(diào)用的,用來把原型加到框架中,而另一個(gè)構(gòu)造函數(shù)是給clone函數(shù)使用的。如果只有一個(gè)默認(rèn)構(gòu)造函數(shù),當(dāng)調(diào)用clone函數(shù)是,則會造成原型重復(fù)加入框架,因此這里需要兩個(gè)構(gòu)造函數(shù)。

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

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