1. 程式人生 > 實用技巧 >[轉+總結]C++ 常用設計模式

[轉+總結]C++ 常用設計模式

C++ 常用設計模式

目錄

背景

設計模式是來源於工業實踐的重要開發經驗,它實際上是面向物件的資料結構,掌握設計模式是掌握面向物件設計的根本要求。

原文:

《C++ 常用設計模式》 (已經根據比較好的學習順序進行了排序)

1、工廠模式(Factory)

在工廠模式中,我們在建立物件時不會對客戶端暴露建立邏輯,並且是通過使用一個共同的介面來指向新建立的物件。工廠模式作為一種建立模式,一般在建立複雜物件時,考慮使用;在建立簡單物件時,建議直接new完成一個例項物件的建立。

1.1、簡單工廠模式

主要特點是需要在工廠類中做判斷,從而創造相應的產品,當增加新產品時,需要修改工廠類。使用簡單工廠模式,我們只需要知道具體的產品型號就可以建立一個產品。

缺點:工廠類集中了所有產品類的建立邏輯,如果產品量較大,會使得工廠類變的非常臃腫


class Tank{}
class Tank56 : public Tank{};

class TankFactory{
public:
    Tank* createTank(Tank_Type type){
        switch(type){
        case Tank_Type_56:
            return new Tank56();
        default:
            return nullptr;
        }
    }
};

1.2、工廠方法模式

定義一個建立物件的介面,其子類去具體現實這個介面以完成具體的建立工作。如果需要增加新的產品類,只需要擴充套件一個相應的工廠類即可。

缺點:產品類資料較多時,需要實現大量的工廠類,這無疑增加了程式碼量。


class Tank{};
class Tank56 : public Tank{};

class TankFactory{
public:
    virtual Tank* createTank() = 0;
};

class Tank56Factory : public TankFactory{
public:
    Tank* createTank() override{
        return new Tank56();
    }
};


1.3、抽象工廠模式

抽象工廠模式提供建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類

當存在多個產品系列,而客戶端只使用一個系列的產品時,可以考慮使用抽象工廠模式。

缺點:當增加一個新系列的產品時,不僅需要現實具體的產品類,還需要增加一個新的建立介面,擴充套件相對困難


class Coat{
public:
    virtual const string& color() = 0;
};

//上衣類
class BlackCoat : public Coat{};
class WhiteCoat : public Coat{}; 

//抽象褲子類
class Pants{
public:
    virtual const string& color() = 0;
};

class BlackPants : public Pants{}; 
class WhitePants : public Pants{}; 

//抽象工廠類,提供衣服建立介面
class Factory{
public:
    virtual Coat* createCoat() = 0;
    virtual Pants* createPants() = 0;
};

//建立白色衣服的工廠類,具體實現建立白色上衣和白色褲子的介面
class WhiteFactory : public Factory{
        // return new WhiteCoat();
        // return new WhitePants();
};

2、單例模式(Singleton)

單例模式顧名思義,保證類僅有一個例項化物件,且提供一個可以訪問它的全域性介面。實現單例模式必須注意一下幾點:

  • 單例類僅有一個例項化物件。
  • 單例類必須自己提供一個例項化物件。
  • 單例類必須提供一個可以訪問唯一例項化物件的介面。

單例模式分為懶漢和餓漢兩種實現方式。

2.1、懶漢單例模式

懶漢:故名思義,不到萬不得已就不會去例項化類,也就是說在第一次用到類例項的時候才會去例項化一個物件。在訪問量較小,甚至可能不會去訪問的情況下,採用懶漢實現,這是以時間換空間。

2.1.1、非執行緒安全的懶漢單例模式

//懶漢式,執行緒非安全版本,需要delete,用智慧指標
class Singleton{
    Singleton(){}                                    //建構函式私有
    Singleton(const Singleton&) = delete;            //明確拒絕
    Singleton& operator=(const Singleton&) = delete; //明確拒絕
public:
    static Singleton* getInstance();
    static Singleton& getInstance();//C++11,返回一個reference指向local static物件

    static Singleton* m_instance;// 靜態(static)成員
};

Singleton* Singleton::m_instance=nullptr; //用時建立,執行緒不安全
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

2.1.2、執行緒安全的懶漢單例模式

//懶漢,執行緒安全版本,但鎖的代價過高
Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

2.1.3、雙檢查鎖,執行緒非安全版本,但由於記憶體讀寫reorder不安全

//std::mutex sm_mutex;
//雙檢查鎖,執行緒非安全版本,但由於記憶體讀寫reorder不安全
Singleton* Singleton::getInstance() {

    if(m_instance==nullptr){
        Lock lock;//std::lock_guard<std::mutex> guard(sm_mutex);
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

2.1.4、返回一個reference指向local static物件

C++11之前,這種單例模式實現方式多執行緒可能存在不確定性:任何一種non-const static物件,不論它是local或non-local,在多執行緒環境下“等待某事發生”都會有麻煩。

解決的方法:在程式的單執行緒啟動階段手工呼叫所有reference-returning函式。這種實現方式的好處是不需要去delete它。

C++11,若當變數在初始化時,併發同時進入宣告語句,併發執行緒將會阻塞等待初始化結束。所以具有執行緒安全性。


//C++11,若當變數在初始化時,併發同時進入宣告語句,併發執行緒將會阻塞等待初始化結束。所以具有執行緒安全性。
Singleton& Singleton::getInstance()
{
    static Singleton singleton;
    return singleton;
}

2.1.5 volatile方式

//C++ 11版本之後的跨平臺實現 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);//獲取記憶體fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//釋放記憶體fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

2.2、餓漢單例模式

餓漢:餓了肯定要飢不擇食。所以在單例類定義的時候就進行例項化

訪問量比較大,或者可能訪問的執行緒比較多時,採用餓漢實現,可以實現更好的效能。這是以空間換時間。

//餓漢式:執行緒安全,注意一定要在合適的地方去delete它
class Singleton
{
public:
    static Singleton* getInstance();
private:
    Singleton(){}                                    //建構函式私有
    Singleton(const Singleton&) = delete;            //明確拒絕
    Singleton& operator=(const Singleton&) = delete; //明確拒絕

    static Singleton* m_pSingleton;
};

Singleton* Singleton::m_pSingleton = new Singleton();

Singleton* Singleton::getInstance(){
    return m_pSingleton;
}

3、外觀模式(Facade)

外觀模式:為子系統中的一組介面定義一個一致的介面;外觀模式提供一個高層的介面,這個介面使得這一子系統更加容易被使用;對於複雜的系統,系統為客戶端提供一個簡單的介面,把負責的實現過程封裝起來,客戶端不需要連線系統內部的細節。

以下情形建議考慮外觀模式:

  • 設計初期階段,應有意識的將不同層分離,層與層之間建立外觀模式
  • 開發階段,子系統越來越複雜,使用外觀模式提供一個簡單的呼叫介面。
  • 一個系統可能已經非常難易維護和擴充套件,但又包含了非常重要的功能,可以為其開發一個外觀類,使得新系統可以方便的與其互動。

優點:

  • 實現了子系統與客戶端之間的鬆耦合關係。
  • 客戶端遮蔽了子系統元件,減少了客戶端所需要處理的物件資料,使得子系統使用起來更方便容易。
  • 更好的劃分了設計層次,對於後期維護更加的容易。

#include <iostream>
using namespace std;

class Control{
public:
    virtual void start() = 0;
    virtual void shutdown() = 0;
};


class Host : public Control{};
class LCDDisplay : public Control{};
class Peripheral : public Control{};

class Computer{
public:
    void start()
    {
        m_host.start();
        m_display.start();
        m_peripheral.start();
        cout << "Computer start" << endl;
    }
private:
    Host   m_host;
    LCDDisplay m_display;
    Peripheral   m_peripheral;
};


4、模板模式(Template)

模板模式:定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟

當多個類有相同的方法,並且邏輯相同,只是細節上有差異時,可以考慮使用模板模式。具體的實現上可以將相同的核心演算法設計為模板方法,具體的實現細節有子類實現。

缺點:每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大

以生產電腦為例,電腦生產的過程都是一樣的,只是一些裝配的器件可能不同而已。

/*
* 關鍵程式碼:在抽象類實現通用介面,細節變化在子類實現。
*/
#include <iostream>
#include <unique_ptr>
using namespace std;

class Computer{
public:
    void product(){
        installCpu();
        installRam();
        installGraphicsCard();
    }
protected:
    virtual void installCpu() = 0;
    void installRam(){cout << "Computer install 16G Ram" << endl;}
};

class ComputerA : public Computer{
protected:
    void installCpu() override{
        cout << "ComputerA install Inter Core i5" << endl;
    }
};

class ComputerB : public Computer{
protected:
    void installCpu() override{
        cout << "ComputerB install Inter Core i7" << endl;
    }
};

int main(){
    unique_ptr<ComputerB> ptest(new ComputerB());
    ptest->product();
    return 0;
}

5、組合模式(Composite)

組合模式:將物件組合成樹形結構以表示“部分-整體”的層次結構,組合模式使得客戶端對單個物件和組合物件的使用一致

既然講到以樹形結構表示“部分-整體”,那可以將組合模式想象成一根大樹,將大樹分成樹枝和樹葉兩部分樹枝上可以再長樹枝,也可以長樹葉

以下情況可以考慮使用組合模式:

  • 希望表示物件的部分-整體層次結構。
  • 希望客戶端忽略組合物件與單個物件的不同,客戶端將統一的使用組合結構中的所有物件。
class Component{//介面
public:
    virtual void process() = 0;
    virtual ~Component(){}
};

//樹節點
class Composite : public Component{//繼承介面Component
    string name;
    list<Component*> elements;//組合,
public:
    Composite(const string & s) : name(s) {}
    void add(Component* element) {
        elements.push_back(element);
    }
    void remove(Component* element){
        elements.remove(element);
        // if((*iter).get()->name() == strName)
    }
    
    void process(){
        //1. process current node
        //2. process leaf nodes
        for (auto &e : elements)
            e->process(); //多型呼叫
    }
};

//葉子節點
class Leaf : public Component{
    string name;
public:
    Leaf(string s) : name(s) {}
    void process(){
        //process current node
    }
};


void Invoke(Component & c){
    //...
    c.process();
    //...
}


int main()
{

    Composite root("root");
    Composite treeNode1("treeNode1");
    Composite treeNode2("treeNode2");
    Composite treeNode3("treeNode3");
    Composite treeNode4("treeNode4");
    Leaf leat1("left1");
    Leaf leat2("left2");
    
    root.add(&treeNode1);
    treeNode1.add(&treeNode2);
    treeNode2.add(&leaf1);
    
    root.add(&treeNode3);
    treeNode3.add(&treeNode4);
    treeNode4.add(&leaf2);
    
    process(root);
    process(leaf2);
    process(treeNode3);
  
}

6、代理模式

代理模式:為其它物件提供一種代理以控制這個物件的訪問。在某些情況下,一個物件不適合或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介作用。

優點:

  • 職責清晰。真實的角色只負責實現實際的業務邏輯,不用關心其它非本職責的事務,通過後期的代理完成具體的任務。這樣程式碼會簡潔清晰。
  • 代理物件可以在客戶端和目標物件之間起到中介的作用,這樣就保護了目標物件
  • 擴充套件性好。

class ISubject{
public:
    virtual void process();
};

class RealSubject: public ISubject{
public:
    virtual void process(){}
};

//Proxy的設計
class SubjectProxy: public ISubject{
public:
    virtual void process(){}
};

// ClientApp
class ClientApp{
    ISubject* subject;
public:
    ClientApp(){
        subject=new SubjectProxy();
       // subject=new RealSubject();
    }
    void DoTask(){
        subject->process();
    }
};

7、觀察者模式(Observer)

觀察者模式:定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都要得到通知並自動更新

觀察者模式從根本上講必須包含兩個角色:觀察者和被觀察物件。

  • 被觀察物件自身應該包含一個容器來存放觀察者物件,當被觀察者自身發生改變時通知容器內所有的觀察者物件自動更新。
  • 觀察者物件可以註冊到被觀察者的中,完成註冊後可以檢測被觀察者的變化,接收被觀察者的通知。
  • 當然觀察者也可以被登出掉,停止對被觀察者的監控。
/*
* 關鍵程式碼:在目標類中增加一個ArrayList來存放觀察者們。
*/
#include <iostream>
#include <list>
#include <memory>

using namespace std;
class View;

//被觀察者抽象類   資料模型
class DataModel{
public:
    virtual ~DataModel(){}
    virtual void addView(View* view) = 0;
    virtual void removeView(View* view) = 0;
    virtual void notify() = 0;   //通知函式
};

//觀察者抽象類   檢視
class View{
public:
    virtual ~View(){ cout << "~View()" << endl; }
    virtual void update() = 0;
};

//具體的被觀察類, 整數模型
class IntDataModel:public DataModel{
public:
    ~IntDataModel(){
        m_pViewList.clear();
    }

    virtual void addView(View* view) override{
        shared_ptr<View> temp(view);
        auto iter = find(m_pViewList.begin(), m_pViewList.end(), temp);
        if(iter == m_pViewList.end()){
            m_pViewList.push_front(temp);
        }
        else{
            cout << "View already exists" << endl;
        }
    }

    void removeView(View* view) override{
        auto iter = m_pViewList.begin();
        for(; iter != m_pViewList.end(); iter++){
            if((*iter).get() == view){
                m_pViewList.erase(iter);
                cout << "remove view" << endl;
                return;
            }
        }
    }

    virtual void notify() override{
        auto iter = m_pViewList.begin();
        for(; iter != m_pViewList.end(); iter++){
            (*iter).get()->update(); //重點~~
        }
    }

private:
    list<shared_ptr<View>> m_pViewList; 
};

//具體的觀察者類    表檢視
class TableView : public View{
public:
    TableView() : m_name("unknow"){}
    TableView(const string& name) : m_name(name){}
    ~TableView(){ cout << "~TableView(): " << m_name.data() << endl; }
    void update() override{
        cout << m_name.data() << " update" << endl;
    }

private:
    string m_name;
};

int main()
{
    /*
    * 這裡需要補充說明的是在此示例程式碼中,View一旦被註冊到DataModel類之後,DataModel解析時會自動解析掉     * 內部容器中儲存的View物件,因此註冊後的View物件不需要在手動去delete,再去delete View物件會出錯。
    */
    
    View* v1 = new TableView("TableView1");
    View* v2 = new TableView("TableView2");
    View* v3 = new TableView("TableView3");
    View* v4 = new TableView("TableView4");

    IntDataModel* model = new IntDataModel;
    model->addView(v1);
    model->addView(v2);
    model->addView(v3);
    model->addView(v4);

    model->notify();

    cout << "-------------\n" << endl;

    model->removeView(v1);

    model->notify();

    delete model;
    model = nullptr;

    return 0;
}

8、策略模式(Strategy)

策略模式是指定義一系列的演算法,把它們單獨封裝起來,並且使它們可以互相替換,使得演算法可以獨立於使用它的客戶端而變化,也是說這些演算法所完成的功能型別是一樣的,對外介面也是一樣的,只是不同的策略為引起環境角色環境角色表現出不同的行為。

相比於使用大量的if...else,使用策略模式可以降低複雜度,使得程式碼更容易維護。

缺點:可能需要定義大量的策略類,並且這些策略類都要提供給客戶端。

[環境角色] 持有一個策略類的引用,最終給客戶端呼叫。

8.1、傳統的策略模式實現


#include <iostream>
using namespace std;

//抽象策略類
class Hurt{
public:
    virtual void blood() = 0;
};

//Adc持續普通攻擊
class AdcHurt : public Hurt{
public:
    void blood() override{cout << "Adc hurt, Blood loss" << endl;}
};

//Apc技能攻擊
class ApcHurt : public Hurt{
public:
    void blood() override{cout << "Apc Hurt, Blood loss" << endl;}
};

//環境角色類, 遊戲角色戰士,傳入一個策略類指標引數。
class Soldier{
public:
    Soldier(Hurt* hurt):m_pHurt(hurt){}
    //在不同的策略下,該遊戲角色表現出不同的攻擊
    void attack(){
        m_pHurt->blood();
    }
private:
    Hurt* m_pHurt;
};

//定義策略標籤
typedef enum
{
    Hurt_Type_Adc,
    Hurt_Type_Apc,
    Hurt_Type_Num
}HurtType;

//環境角色類, 遊戲角色法師,傳入一個策略標籤引數。
class Mage{
public:
    Mage(HurtType type){
        switch(type){
        case Hurt_Type_Adc:
            m_pHurt = new AdcHurt();
            break;
        default:
            break;
        }
    }
    ~Mage(){
        delete m_pHurt;
        m_pHurt = nullptr;
        cout << "~Mage()" << endl;
    }

    void attack(){m_pHurt->blood();}
private:
    Hurt* m_pHurt;
};

// 遊戲角色弓箭手,實現模板傳遞策略。
template<typename T>
class Archer{
public:
    void attack(){m_hurt.blood();}
private:
    T m_hurt;
};

int main()
{
    Archer<ApcHurt>* arc = new Archer<ApcHurt>;
    arc->attack();

    delete arc;
    arc = nullptr;
    
    return 0;
}

8.2、使用函式指標實現策略模式

#include <iostream>
#include <functional> 

void adcHurt(){
    std::cout << "Adc Hurt" << std::endl;
}

void apcHurt(){
    std::cout << "Apc Hurt" << std::endl;
}

//環境角色類, 使用傳統的函式指標
class Soldier{
public:
    typedef void (*Function)();
    Soldier(Function fun): m_fun(fun){}
    void attack(){m_fun();}
private:
    Function m_fun;
};

//環境角色類, 使用std::function<>
class Mage{
public:
    typedef std::function<void()> Function;

    Mage(Function fun): m_fun(fun){}
    void attack(){m_fun();}
private:
    Function m_fun;
};

int main(){
    Soldier* soldier = new Soldier(apcHurt);
    soldier->attack();
    delete soldier;
    soldier = nullptr;
    return 0;
}

9、建造者模式(Builder)

建造者模式:將複雜物件的構建和其表示分離,使得相同的構建過程可以產生不同的表示

以下情形可以考慮使用建造者模式:

  • 物件的建立複雜,但是其各個部分的子物件建立演算法一定。
  • 需求變化大,構造複雜物件的子物件經常變化,但將其組合在一起的演算法相對穩定

建造者模式的優點:

  • 將物件的建立和表示分離,客戶端不需要了解具體的構建細節
  • 增加新的產品物件時,只需要增加其具體的建造類即可,不需要修改原來的程式碼,擴充套件方便。

產品之間差異性大,內部變化較大、較複雜時不建議使用建造者模式。

class House{
    //....
};
class StoneHouse: public House{};

class HouseBuilder {
public:
    House* GetResult(){ return pHouse;  }
    virtual ~HouseBuilder(){}
protected:
    House* pHouse;
    virtual void BuildPart1()=0;
    virtual void BuildPart2()=0;
    virtual void BuildPart3()=0;
};
//建造者類:建立和提供例項;
class StoneHouseBuilder: public HouseBuilder{
protected:
    virtual void BuildPart1(){/* pHouse->Part1 = ...;*/}
    virtual void BuildPart2(){}
    virtual void BuildPart3(){}
};

// Director類:管理建造出來的例項的依賴關係。

class HouseDirector{
public:
    HouseBuilder* pHouseBuilder;

    HouseDirector(HouseBuilder* pHouseBuilder){
        this->pHouseBuilder=pHouseBuilder;
    }

    House* Construct(){
        pHouseBuilder->BuildPart1();
        for (int i = 0; i < 4; i++){
            pHouseBuilder->BuildPart2();
        }
        bool flag=pHouseBuilder->BuildPart3();
        if(flag){
            pHouseBuilder->BuildPart4();
        }
        pHouseBuilder->BuildPart5();
        return pHouseBuilder->GetResult();
    }
};

10、介面卡模式(Adapter)

介面卡模式可以將一個類的介面轉換成客戶端希望的另一個介面,使得原來由於介面不相容而不能在一起工作的那些類可以在一起工作。通俗的講就是當我們已經有了一些類,而這些類不能滿足新的需求,此時就可以考慮是否能將現有的類適配成可以滿足新需求的類。介面卡類需要繼承或依賴已有的類,實現想要的目標介面。

缺點:過多地使用介面卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到呼叫的是 A 介面,其實內部被適配成了 B 介面的實現,一個系統如果太多出現這種情況,無異於一場災難。

因此如果不是很有必要,可以不使用介面卡,而是直接對系統進行重構

9.1、使用複合實現介面卡模式

/*
* 關鍵程式碼:介面卡繼承或依賴已有的物件,實現想要的目標介面。
* 以下示例中,假設我們之前有了一個雙端佇列,新的需求要求使用棧和佇列來完成。
  雙端佇列可以在頭尾刪減或增加元素。而棧是一種先進後出的資料結構,新增資料時新增到棧的頂部,刪除資料時先刪   除棧頂部的資料。因此我們完全可以將一個現有的雙端佇列適配成一個棧。
*/

//雙端佇列, 被適配類
class Deque
{
public:
    void push_back(int x)
    {
        cout << "Deque push_back:" << x << endl;
    }
    void push_front(int x)
    {
        cout << "Deque push_front:" << x << endl;
    }
    void pop_back()
    {
        cout << "Deque pop_back" << endl;
    }
    void pop_front()
    {
        cout << "Deque pop_front" << endl;
    }
};

//順序類,抽象目標類
class Sequence  
{
public:
    virtual void push(int x) = 0;
    virtual void pop() = 0;
};

//棧,後進先出, 適配類
class Stack:public Sequence   
{
public:
    //將元素新增到堆疊的頂部。
    void push(int x) override
    {
        m_deque.push_front(x);
    }
    //從堆疊中刪除頂部元素
    void pop() override
    {
        m_deque.pop_front();
    }
private:
    Deque m_deque;
};

//佇列,先進先出,適配類
class Queue:public Sequence  
{
public:
    //將元素新增到佇列尾部
    void push(int x) override
    {
        m_deque.push_back(x);
    }
    //從佇列中刪除頂部元素
    void pop() override
    {
        m_deque.pop_front();
    }
private:
    Deque m_deque;
};

9.2、使用繼承實現介面卡模式

//雙端佇列,被適配類
class Deque  
{
public:
    void push_back(int x)
    {
        cout << "Deque push_back:" << x << endl;
    }
    void push_front(int x)
    {
        cout << "Deque push_front:" << x << endl;
    }
    void pop_back()
    {
        cout << "Deque pop_back" << endl;
    }
    void pop_front()
    {
        cout << "Deque pop_front" << endl;
    }
};

//順序類,抽象目標類
class Sequence  
{
public:
    virtual void push(int x) = 0;
    virtual void pop() = 0;
};

//棧,後進先出, 適配類
class Stack:public Sequence, private Deque   
{
public:
    void push(int x)
    {
        push_front(x);
    }
    void pop()
    {
        pop_front();
    }
};

//佇列,先進先出,適配類
class Queue:public Sequence, private Deque 
{
public:
    void push(int x)
    {
        push_back(x);
    }
    void pop()
    {
        pop_front();
    }
};

11、橋接模式(Bridge)

橋接模式:將抽象部分與實現部分分離,使它們都可以獨立變換。

以下情形考慮使用橋接模式:

  • 當一個物件有多個變化因素的時候,考慮依賴於抽象的實現,而不是具體的實現。
  • 當多個變化因素在多個物件間共享時,考慮將這部分變化的部分抽象出來再聚合/合成進來。
  • 當一個物件的多個變化因素可以動態變化的時候。

優點:

  • 將實現抽離出來,再實現抽象,使得物件的具體實現依賴於抽象,滿足了依賴倒轉原則。
  • 更好的可擴充套件性。
  • 可動態的切換實現。橋接模式實現了抽象和實現的分離,在實現橋接模式時,就可以實現動態的選擇具體的實現。
/*
* 關鍵程式碼:將現實獨立出來,抽象類依賴現實類。
* 以下示例中,將各類App、各類手機獨立開來,實現各種App和各種手機的自由橋接。
*/
#include <iostream>

using namespace std;

//抽象App類,提供介面
class App{
public:
    virtual ~App(){ cout << "~App()" << endl; }
    virtual void run() = 0;
};

//具體的App實現類
class GameApp:public App{
public:
    void run()
    {
        cout << "GameApp Running" << endl;
    }
};

//具體的App實現類
class TranslateApp:public App{
public:
    void run(){
        cout << "TranslateApp Running" << endl;
    }
};

//抽象手機類,提供介面
class MobilePhone{
public:
    virtual ~MobilePhone(){ cout << "~MobilePhone()" << endl;}
    virtual void appRun(App* app) = 0;  //實現App與手機的橋接
};

//具體的手機實現類
class XiaoMi:public MobilePhone{
public:
    void appRun(App* app){
        cout << "XiaoMi: ";
        app->run();
    }
};

//具體的手機實現類
class HuaWei:public MobilePhone{
public:
    void appRun(App* app){
        cout << "HuaWei: ";
        app->run();
    }
};

int main(){
    App* gameApp = new GameApp;
    App* translateApp = new TranslateApp;
    MobilePhone* mi = new XiaoMi;
    MobilePhone* hua = new HuaWei;
    mi->appRun(gameApp);
    mi->appRun(translateApp);
    hua->appRun(gameApp);
    hua->appRun(translateApp);

    delete hua;
    hua = nullptr;
    ...

    return 0;
}

12、裝飾模式(Decorator)

裝飾模式:動態地給一個物件新增一些額外的功能,它是通過建立一個包裝物件,也就是裝飾來包裹真實的物件。新增加功能來說,裝飾器模式比生產子類更加靈活。

以下情形考慮使用裝飾模式:

  • 需要擴充套件一個類的功能,或給一個類新增附加職責。
  • 需要動態的給一個物件新增功能,這些功能可以再動態的撤銷。
  • 需要增加由一些基本功能的排列組合而產生的非常大量的功能,從而使繼承關係變的不現實。
  • 當不能採用生成子類的方法進行擴充時。一種情況是,可能有大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長。另一種情況可能是因為類定義被隱藏,或類定義不能用於生成子類。
void Process(){
    //執行時裝配
    FileStream* s1 =new FileStream();
    CryptoStream* s2 = new CryptoStream(s1);
    BufferedStream* s3 = new BufferedStream(s1);
    BufferedStream* s4 = new BufferedStream(s2);
}

13、中介者模式(Mediator)

中介者模式:用一箇中介物件來封裝一系列的物件互動,中介者使各物件不需要顯示地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之前的互動。

如果物件與物件之前存在大量的關聯關係,若一個物件改變,常常需要跟蹤與之關聯的物件,並做出相應的處理,這樣勢必會造成系統變得複雜,遇到這種情形可以考慮使用中介者模式。當多個物件存在關聯關係時,為它們設計一箇中介物件,當一個物件改變時,只需要通知它的中介物件,再由它的中介物件通知每個與它相關的物件。

/*
* 關鍵程式碼:將相關物件的通訊封裝到一個類中單獨處理。
*/
#include <iostream>

using namespace std;

class Mediator;

//抽象同事類。
class Businessman
{
public:
    Businessman(){}
    Businessman(Mediator* mediator) : m_pMediator(mediator){}

    virtual ~Businessman(){}

    virtual void setMediator(Mediator* m)
    {
        m_pMediator = m;
    }

    virtual void sendMessage(const string& msg) = 0;
    virtual void getMessage(const string& msg) = 0;

protected:
    Mediator* m_pMediator;
};

//抽象中介者類。
class  Mediator
{
public:
    virtual ~Mediator(){}
    virtual void setBuyer(Businessman* buyer) = 0;
    virtual void setSeller(Businessman* seller) = 0;
    virtual void send(const string& msg, Businessman* man) = 0;
};

//具體同事類
class Buyer : public Businessman
{
public:
    Buyer() : Businessman(){}
    Buyer(Mediator* mediator) : Businessman(mediator){}

    void sendMessage(const string& msg) override
    {
        m_pMediator->send(msg, this);
    }

    void getMessage(const string& msg)
    {
        cout << "Buyer recv: " << msg.data() << endl;
    }
};

//具體同事類
class Seller : public Businessman
{
public:
    Seller() : Businessman(){}
    Seller(Mediator* mediator) : Businessman(mediator){}

    void sendMessage(const string& msg) override
    {
        m_pMediator->send(msg, this);
    }

    void getMessage(const string& msg)
    {
        cout << "Seller recv: " << msg.data() << endl;
    }
};

//具體中介者類
class HouseMediator : public Mediator
{
public:
    void setBuyer(Businessman* buyer) override
    {
        m_pBuyer = buyer;
    }

    void setSeller(Businessman* seller) override
    {
        m_pSeller = seller;
    }

    void send(const string& msg, Businessman* man) override
    {
        if(man == m_pBuyer)
        {
            m_pSeller->getMessage(msg);
        }
        else if(man == m_pSeller)
        {
            m_pBuyer->getMessage(msg);
        }
    }

private:
    Businessman* m_pBuyer;
    Businessman* m_pSeller;
};

int main()
{
    HouseMediator* hMediator = new HouseMediator;
    Buyer* buyer = new Buyer(hMediator);
    Seller* seller = new Seller(hMediator);

    hMediator->setBuyer(buyer);
    hMediator->setSeller(seller);

    buyer->sendMessage("Sell not to sell?");
    seller->sendMessage("Of course selling!");

    delete buyer;
    buyer = nullptr;

    delete seller;
    seller = nullptr;

    delete hMediator;
    hMediator = nullptr;


    return 0;
}

14、備忘錄模式(Memento)

備忘錄模式:在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。這樣以後就可以將該物件恢復到原來儲存的狀態。

備忘錄模式中需要定義的角色類:

  1. Originator(發起人):負責建立一個備忘錄Memento,用以記錄當前時刻自身的內部狀態,並可使用備忘錄恢復內部狀態。Originator可以根據需要決定Memento儲存自己的哪些內部狀態。
  2. Memento(備忘錄):負責儲存Originator物件的內部狀態,並可以防止Originator以外的其他物件訪問備忘錄。備忘錄有兩個介面:Caretaker只能看到備忘錄的窄介面,他只能將備忘錄傳遞給其他物件。Originator卻可看到備忘錄的寬介面,允許它訪問返回到先前狀態所需要的所有資料。
  3. Caretaker(管理者):負責備忘錄Memento,不能對Memento的內容進行訪問或者操作。
/*
* 關鍵程式碼:Memento類、Originator類、Caretaker類;Originator類不與Memento類耦合,而是與Caretaker類耦合。
*/

include <iostream>

using namespace std;

//需要儲存的資訊
typedef struct  
{
    int grade;
    string arm;
    string corps;
}GameValue;

//Memento類
class Memento   
{
public:
    Memento(){}
    Memento(GameValue value):m_gameValue(value){}
    GameValue getValue()
    {
        return m_gameValue;
    }
private:
    GameValue m_gameValue;
};

//Originator類
class Game   
{
public:
    Game(GameValue value):m_gameValue(value)
    {}
    void addGrade()  //等級增加
    {
        m_gameValue.grade++;
    }
    void replaceArm(string arm)  //更換武器
    {
        m_gameValue.arm = arm;
    }
    void replaceCorps(string corps)  //更換工會
    {
        m_gameValue.corps = corps;
    }
    Memento saveValue()    //儲存當前資訊
    {
        Memento memento(m_gameValue);
        return memento;
    }
    void load(Memento memento) //載入資訊
    {
        m_gameValue = memento.getValue();
    }
    void showValue()
    {
        cout << "Grade: " << m_gameValue.grade << endl;
        cout << "Arm  : " << m_gameValue.arm.data() << endl;
        cout << "Corps: " << m_gameValue.corps.data() << endl;
    }
private:
    GameValue m_gameValue;
};

//Caretaker類
class Caretake 
{
public:
    void save(Memento memento)  //儲存資訊
    {
        m_memento = memento;
    }
    Memento load()            //讀已儲存的資訊
    {
        return m_memento;
    }
private:
    Memento m_memento;
};

int main()
{
    GameValue v1 = {0, "Ak", "3K"};
    Game game(v1);    //初始值
    game.addGrade();
    game.showValue();
    cout << "----------" << endl;
    Caretake care;
    care.save(game.saveValue());  //儲存當前值
    game.addGrade();          //修改當前值
    game.replaceArm("M16");
    game.replaceCorps("123");
    game.showValue();
    cout << "----------" << endl;
    game.load(care.load());   //恢復初始值
    game.showValue();
    return 0;
}

15、原型模式(Prototype)

原型模式:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。通俗的講就是當需要建立一個新的例項化物件時,我們剛好有一個例項化物件,但是已經存在的例項化物件又不能直接使用。這種情況下拷貝一個現有的例項化物件來用,可能會更方便。

以下情形可以考慮使用原型模式:

  • 當new一個物件,非常繁瑣複雜時,可以使用原型模式來進行復制一個物件。比如建立物件時,建構函式的引數很多,而自己又不完全的知道每個引數的意義,就可以使用原型模式來建立一個新的物件,不必去理會建立的過程。
  • 當需要new一個新的物件,這個物件和現有的物件區別不大,我們就可以直接複製一個已有的物件,然後稍加修改。
  • 當需要一個物件副本時,比如需要提供物件的資料,同時又需要避免外部對資料物件進行修改,那就拷貝一個物件副本供外部使用。
/*
* 關鍵程式碼:拷貝,return new className(*this);
*/
#include <iostream>

using namespace std;

//提供一個抽象克隆基類。
class Clone
{
public:
    virtual Clone* clone() = 0;
    virtual void show() = 0;
};

//具體的實現類
class Sheep:public Clone
{
public:
    Sheep(int id, string name):Clone(),
                               m_id(id),m_name(name)
    {
        cout << "Sheep() id address:" << &m_id << endl;
        cout << "Sheep() name address:" << &m_name << endl;
    }
    ~Sheep()
    {
    }
    //關鍵程式碼拷貝建構函式
    Sheep(const Sheep& obj)
    {
        this->m_id = obj.m_id;
        this->m_name = obj.m_name;
        cout << "Sheep(const Sheep& obj) id address:" << &m_id << endl;
        cout << "Sheep(const Sheep& obj) name address:" << &m_name << endl;
    }
    //關鍵程式碼克隆函式,返回return new Sheep(*this)
    Clone* clone()
    {
        return new Sheep(*this);
    }
    void show()
    {
        cout << "id  :" << m_id << endl;
        cout << "name:" << m_name.data() << endl;
    }
private:
    int m_id;
    string m_name;
};

int main()
{
    Clone* s1 = new Sheep(1, "abs");
    s1->show();
    Clone* s2 = s1->clone();
    s2->show();
    
    delete s1;
    s1 = nullptr;
    delete s2;
    s2 = nullptr;
    return 0;
}

16、享元模式(Flyweight)

享元模式:運用共享技術有效地支援大量細粒度的物件。在有大量物件時,把其中共同的部分抽象出來,如果有相同的業務請求,直接返回記憶體中已有的物件,避免重新建立。

以下情況可以考慮使用享元模式:

  • 系統中有大量的物件,這些物件消耗大量的記憶體,且這些物件的狀態可以被外部化。

對於享元模式,需要將物件的資訊分為兩個部分:內部狀態和外部狀態。內部狀態是指被共享出來的資訊,儲存在享元物件內部且不隨環境變化而改變;外部狀態是不可以共享的,它隨環境改變而改變,是由客戶端控制的。

/*
* 關鍵程式碼:將內部狀態作為標識,進行共享。
*/
#include <iostream>
#include <map>
#include <memory>

using namespace std;

//抽象享元類,提供享元類外部介面。
class AbstractConsumer
{
public:
    virtual ~AbstractConsumer(){}
    virtual void setArticle(const string&) = 0;
    virtual const string& article() = 0;
};

//具體的享元類
class Consumer : public AbstractConsumer
{
public:
    Consumer(const string& strName) : m_user(strName){}
    ~Consumer()
    {
        cout << " ~Consumer()" << endl;
    }

    void setArticle(const string& info) override
    {
        m_article = info;
    }

    const string& article() override
    {
        return m_article;
    }

private:
    string m_user;
    string m_article;
};

//享元工廠類
class Trusteeship
{
public:
    ~Trusteeship()
    {
         m_consumerMap.clear();
    }

    void hosting(const string& user, const string& article)
    {
        if(m_consumerMap.count(user))
        {
            cout << "A customer named " << user.data() << " already exists" << endl;
            Consumer* consumer = m_consumerMap.at(user).get();
            consumer->setArticle(article);
        }
        else
        {
            shared_ptr<Consumer> consumer(new Consumer(user));
            consumer.get()->setArticle(article);
            m_consumerMap.insert(pair<string, shared_ptr<Consumer>>(user, consumer));
        }
    }

    void display()
    {
        map<string, shared_ptr<Consumer>>::iterator iter = m_consumerMap.begin();
        for(; iter != m_consumerMap.end(); iter++)
        {
            cout << iter->first.data() << " : "<< iter->second.get()->article().data() << endl;
        }
    }

private:
    map<string, shared_ptr<Consumer>> m_consumerMap;
};


int main()
{
    Trusteeship* ts = new Trusteeship;
    ts->hosting("zhangsan", "computer");
    ts->hosting("lisi", "phone");
    ts->hosting("wangwu", "watch");

    ts->display();

    ts->hosting("zhangsan", "TT");
    ts->hosting("lisi", "TT");
    ts->hosting("wangwu", "TT");

    ts->display();

    delete ts;
    ts = nullptr;

    return 0;
}

17、職責鏈模式(Chain of Resp.)

職責鏈模式:使多個物件都有機會處理請求,從而避免請求的傳送者和接收者之前的耦合關係,將這些物件連成一條鏈,並沿著這條鏈傳遞請求,直到有一個物件處理它為止。

職責鏈上的處理者負責處理請求,客戶只需要將請求傳送到職責鏈上即可,無需關心請求的處理細節和請求的傳遞,所有職責鏈將請求的傳送者和請求的處理者解耦了。

/*
* 關鍵程式碼:Handler內指明其上級,handleRequest()裡判斷是否合適,不合適則傳遞給上級。
*/
#include <iostream>

using namespace std;

enum RequestLevel
{
    Level_One = 0,
    Level_Two,
    Level_Three,
    Level_Num
};

//抽象處理者(Handler)角色,提供職責鏈的統一介面。
class Leader
{
public:
    Leader(Leader* leader):m_leader(leader){}
    virtual ~Leader(){}
    virtual void handleRequest(RequestLevel level) = 0;
protected:
    Leader* m_leader;
};

//具體處理者(Concrete Handler)角色
class Monitor:public Leader   //鏈釦1
{
public:
    Monitor(Leader* leader):Leader(leader){}
    void handleRequest(RequestLevel level)
    {
        if(level < Level_Two)
        {
            cout << "Mointor handle request : " << level << endl;
        }
        else
        {
            m_leader->handleRequest(level);
        }
    }
};

//具體處理者(Concrete Handler)角色
class Captain:public Leader    //鏈釦2
{
public:
    Captain(Leader* leader):Leader(leader){}
    void handleRequest(RequestLevel level)
    {
        if(level < Level_Three)
        {
            cout << "Captain handle request : " << level << endl;
        }
        else
        {
            m_leader->handleRequest(level);
        }
    }
};

//具體處理者(Concrete Handler)角色
class General:public Leader   //鏈釦3
{
public:
    General(Leader* leader):Leader(leader){}
    void handleRequest(RequestLevel level)
    {
        cout << "General handle request : " << level << endl;
    }
};

int main()
{
    Leader* general = new General(nullptr);
    Leader* captain = new Captain(general);
    Leader* monitor = new Monitor(captain);
    monitor->handleRequest(Level_One);

    delete monitor;
    monitor = nullptr;
    delete captain;
    captain = nullptr;
    delete general;
    general = nullptr;
    return 0;
}

標籤: 手冊, c/c++