1. 程式人生 > >C++ 常用設計模式(學習筆記)

C++ 常用設計模式(學習筆記)

1、工廠模式:簡單工廠模式、工廠方法模式、抽象工廠模式
  1)、簡單工廠模式:主要特點是需要在工廠類中做判斷,從而創造相應的產品,當增加新產品時,需要修改工廠類。

複製程式碼
typedef enum
{
    T80 = 1,
    T99
}TankType;

class Tank
{
public:
    virtual void message() = 0;
};

class Tank80:public Tank
{
public:
    void message()
    {
        cout << "Tank80" << endl;
    }
};

class
Tank99:public Tank { public: void message() { cout << "Tank99" << endl; } }; class TankFactory { public: Tank* createTank(TankType type) { switch(type) { case 1: return new Tank80(); case 2: return new Tank99();
default: return NULL; } } };
複製程式碼

  2)、工廠方法模式:是指定義一個建立物件的介面,讓子類決定例項化哪一個類,Factory Method使一個類的例項化延遲到其子類。
              主要解決:主要解決介面選擇的問題。
              何時使用:我們明確地計劃不同條件下建立不同例項時。
              如何解決:讓其子類實現工廠介面,返回的也是一個抽象的產品。
      關鍵程式碼:建立過程在其子類執行。
      缺點:每增加一種產品,就需要增加一個物件工廠。相比簡單工廠模式,工廠方法模式需要定義更多的類。

複製程式碼
class Tank
{
public:
    virtual void message() = 0;
};

class Tank80:public Tank
{
public:
    void message()
    {
        cout << "Tank80" << endl;
    }
};

class Tank99:public Tank
{
public:
    void message()
    {
        cout << "Tank99" << endl;
    }
};

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

class Tank80Factory:public TankFactory
{
public:
    Tank* createTank()
    {
        return new Tank80();
    }
};

class Tank99Factory:public TankFactory
{
public:
    Tank* createTank()
    {
        return new Tank99();
    }
};
複製程式碼

  3)、抽象工廠模式:提供一個建立一系列相關或相互依賴的物件介面,而無需指定它們的具體類。
      主要解決:主要解決介面選擇的問題。
      何時使用:系統的產品有多於一個的產品族,而系統只消費其中某一族的產品。
      如何解決:在一個產品族裡面,定義多個產品。
      關鍵程式碼:在一個工廠裡聚合多個同類產品。
      缺點:產品族擴充套件非常困難,要增加一個系列的某一產品,既要在抽象的 Creator 里加程式碼,又要在具體的裡面加程式碼。

複製程式碼
class Tank
{
public:
    virtual void message() = 0;
};

class Tank80:public Tank
{
public:
    void message()
    {
        cout << "Tank80" << endl;
    }
};

class Tank99:public Tank
{
public:
    void message()
    {
        cout << "Tank99" << endl;
    }
};

class Plain
{
public:
    virtual void message() = 0;
};

class Plain80: public Plain
{
public:
    void message()
    {
        cout << "Plain80" << endl;
    }
};

class Plain99: public Plain
{
public:
    void message()
    {
        cout << "Plain99" << endl;
    }
};

class Factory
{
public:
    virtual Tank* createTank() = 0;
    virtual Plain* createPlain() = 0;
};

class Factory80:public Factory
{
public:
    Tank* createTank()
    {
        return new Tank80();
    }
    Plain* createPlain()
    {
        return new Plain80();
    }
};

class Factory99:public Factory
{
public:
    Tank* createTank()
    {
        return new Tank99();
    }
    Plain* createPlain()
    {
        return new Plain99();
    }
};
複製程式碼

2、 策略模式:是指定義一系列的演算法,把它們一個個封裝起來,並且使它們可以互相替換。使得演算法可以獨立於使用它的客戶而變化,也就是說這些演算法所完成的功能是一樣的,對外介面是一樣的,只是各自現實上存在差異。
    主要解決:在有多種演算法相似的情況下,使用 if...else 所帶來的複雜和難以維護。
    何時使用:一個系統有許多許多類,而區分它們的只是他們直接的行為。
    如何解決:將這些演算法封裝成一個一個的類,任意地替換。
    關鍵程式碼:實現同一個介面。
    缺點: 1、策略類會增多。 2、所有策略類都需要對外暴露。

複製程式碼
//傳統策略模式實現
class Hurt
{
public:
    virtual void redBuff() = 0;
};

class AdcHurt:public Hurt
{
public:
    void redBuff()
    {
        cout << "Adc hurt" << endl;
    }
};

class ApcHurt:public Hurt
{
public:
    void redBuff()
    {
        cout << "Apc hurt" << endl;
    }
};

//方法1:傳入一個指標引數
class Soldier
{
public:
    Soldier(Hurt* hurt):m_hurt(hurt)
    {
    }
    ~Soldier()
    {
    }
    void beInjured()
    {
        m_hurt->redBuff();
    }
private:
    Hurt* m_hurt;
};

//方法2:傳入一個引數標籤
typedef enum
{
    adc,
    apc
}HurtType;

class Master
{
public:
    Master(HurtType type)
    {
        switch(type)
        {
        case adc:
            m_hurt = new AdcHurt;
            break;
        case apc:
            m_hurt = new ApcHurt;
            break;
        default:
            m_hurt = NULL;
            break;
        }
    }
    ~Master()
    {
    }
    void beInjured()
    {
        if(m_hurt != NULL)
        {
            m_hurt->redBuff();
        }
        else
        {
            cout << "Not hurt" << endl;
        }
    }
private:
    Hurt* m_hurt;
};

//方法3:使用模板類
template <typename T>
class Tank
{
public:
    void beInjured()
    {
        m_hurt.redBuff();
    }
private:
    T m_hurt;
};
//END

//使用函式指標實現策略模式
void adcHurt(int num)
{
    cout << "adc hurt:" << num << endl;
}

void apcHurt(int num)
{
    cout << "apc hurt:" << num << endl;
}

//普通函式指標
class Aid
{
public:
    typedef void (*HurtFun)(int);
    
    Aid(HurtFun fun):m_fun(fun)
    {
    }
    void beInjured(int num)
    {
        m_fun(num);
    }
private:
    HurtFun m_fun;
};

//使用std::function , 標頭檔案:#include<functional>
class Bowman
{
public:
    typedef std::function<void(int)> HurtFunc;

    Bowman(HurtFunc fun):m_fun(fun)
    {
    }
    void beInjured(int num)
    {
        m_fun(num);
    }

private:
    HurtFunc m_fun;
};
//END
複製程式碼


3、介面卡模式:將一個類的介面轉換成客戶希望的另一個介面,使得原本由於介面不相容而不能一起工作的哪些類可以一起工作。
    主要解決:主要解決在軟體系統中,常常要將一些"現存的物件"放到新的環境中,而新環境要求的介面是現物件不能滿足的。
    何時使用: 1、系統需要使用現有的類,而此類的介面不符合系統的需要。 2、想要建立一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作,這些源類不一定有一致的介面。 3、通過介面轉換,將一個類插入另一個類系中。(比如老虎和飛禽,現在多了一個飛虎,在不增加實體的需求下,增加一個介面卡,在裡面包容一個虎物件,實現飛的介面。)
    如何解決:繼承或依賴(推薦)。
    關鍵程式碼:介面卡繼承或依賴已有的物件,實現想要的目標介面。
    缺點:1、過多地使用介面卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到呼叫的是 A 介面,其實內部被適配成了 B 介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用介面卡,而是直接對系統進行重構。

複製程式碼
//使用複合,物件模式
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)
    {
        m_deque.push_back(x);
    }
    void pop()
    {
        m_deque.pop_back();
    }
private:
    Deque m_deque;
};

class Queue:public Sequence  //佇列,適配類
{
public:
    void push(int x)
    {
        m_deque.push_back(x);
    }
    void pop()
    {
        m_deque.pop_front();
    }
private:
    Deque m_deque;
};
//END
複製程式碼複製程式碼
//使用繼承,類模式
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_back(x);
    }
    void pop()
    {
        pop_back();
    }
};

class Queue:public Sequence, private Deque  //佇列,適配類
{
public:
    void push(int x)
    {
        push_back(x);
    }
    void pop()
    {
        pop_front();
    }
};
//END
複製程式碼

4、 單例模式:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
    主要解決:一個全域性使用的類頻繁地建立與銷燬。
    何時使用:想控制例項數目,節省系統資源的時候。
    如何解決:判斷系統是否已存在單例,如果有則返回,沒有則建立。
    關鍵程式碼:建構函式是私有的。

 單例大約有兩種實現方法:懶漢與餓漢。
    懶漢:故名思義,不到萬不得已就不會去例項化類,也就是說在第一次用到類例項的時候才會去例項化,所以上邊的經典方法被歸為懶漢實現;
    餓漢:餓了肯定要飢不擇食。所以在單例類定義的時候就進行例項化。
 特點與選擇:
    由於要進行執行緒同步,所以在訪問量比較大,或者可能訪問的執行緒比較多時,採用餓漢實現,可以實現更好的效能。這是以空間換時間。
    在訪問量較小時,採用懶漢實現。這是以時間換空間。

複製程式碼
//懶漢式一般實現:非執行緒安全,getInstance返回的例項指標需要delete
class Singleton
{
public:
    static Singleton* getInstance();
    ~Singleton(){}

private:
    static Singleton* m_pSingleton;
    Singleton(){}    
    Singleton(const Singleton& obj) = delete;  //明確拒絕
    Singleton& operator=(const Singleton& obj) = delete; //明確拒絕
};

Singleton* Singleton::m_pSingleton = NULL;

Singleton* Singleton::getInstance()
{
    if(m_pSingleton == NULL)
    {
        m_pSingleton = new Singleton;
    }
    return m_pSingleton;
}
//END

//懶漢式:加lock,執行緒安全
std::mutex mt;

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 = NULL;

Singleton* Singleton::getInstance()
{
    if(m_pSingleton == NULL)
    {
        mt.lock();
        m_pSingleton = new Singleton();
        mt.unlock();
    }
    return m_pSingleton;
}
//END

//返回一個reference指向local static物件
//多執行緒可能存在不確定性:任何一種non-const static物件,不論它是local