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; } }; classTank99: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