設計模式
1、工廠模式
在工廠模式中,我們在創建對象時不會對客戶端暴露創建邏輯,並且是通過使用一個共同的接口來指向新創建的對象。工廠模式作為一種創建模式,一般在創建復雜對象時,考慮使用;在創建簡單對象時,建議直接new完成一個實例對象的創建。
1.1、簡單工廠模式
主要特點是需要在工廠類中做判斷,從而創造相應的產品,當增加新產品時,需要修改工廠類。使用簡單工廠模式,我們只需要知道具體的產品型號就可以創建一個產品。
缺點:工廠類集中了所有產品類的創建邏輯,如果產品量較大,會使得工廠類變的非常臃腫。
1 /* 2 關鍵代碼:創建過程在工廠類中完成。 3 */ 4 5 #include <iostream> 6 7 using namespace std; 8 9 //定義產品類型信息 10 typedef enum 11 { 12 Tank_Type_56, 13 Tank_Type_96, 14 Tank_Type_Num 15 }Tank_Type; 16 17 //抽象產品類 18 class Tank 19 { 20 public: 21 virtual const string& type() = 0; 22 }; 23 24 //具體的產品類 25 class Tank56 : public Tank 26 { 27 public: 28 Tank56():Tank(),m_strType("Tank56") 29 { 30 } 31 32 const string& type() override 33 { 34 cout << m_strType.data() << endl; 35 return m_strType; 36 } 37 private: 38 string m_strType; 39 }; 40 41 //具體的產品類 42 class Tank96 : public Tank 43 { 44 public: 45 Tank96():Tank(),m_strType("Tank96") 46 { 47 } 48 const string& type() override 49 { 50 cout << m_strType.data() << endl; 51 return m_strType; 52 } 53 54 private: 55 string m_strType; 56 }; 57 58 //工廠類 59 class TankFactory 60 { 61 public: 62 //根據產品信息創建具體的產品類實例,返回一個抽象產品類 63 Tank* createTank(Tank_Type type) 64 { 65 switch(type) 66 { 67 case Tank_Type_56: 68 return new Tank56(); 69 case Tank_Type_96: 70 return new Tank96(); 71 default: 72 return nullptr; 73 } 74 } 75 }; 76 77 78 int main() 79 { 80 TankFactory* factory = new TankFactory(); 81 Tank* tank56 = factory->createTank(Tank_Type_56); 82 tank56->type(); 83 Tank* tank96 = factory->createTank(Tank_Type_96); 84 tank96->type(); 85 86 delete tank96; 87 tank96 = nullptr; 88 delete tank56; 89 tank56 = nullptr; 90 delete factory; 91 factory = nullptr; 92 93 return 0; 94 }
1.2、工廠方法模式
定義一個創建對象的接口,其子類去具體現實這個接口以完成具體的創建工作。如果需要增加新的產品類,只需要擴展一個相應的工廠類即可。
缺點:產品類數據較多時,需要實現大量的工廠類,這無疑增加了代碼量。
1 /* 2 關鍵代碼:創建過程在其子類執行。 3 */ 4 5 #include <iostream> 6 7 using namespace std; 8 9 //產品抽象類 10 class Tank 11 { 12 public: 13 virtual const string& type() = 0; 14 }; 15 16 //具體的產品類 17 class Tank56 : public Tank 18 { 19 public: 20 Tank56():Tank(),m_strType("Tank56") 21 { 22 } 23 24 const string& type() override 25 { 26 cout << m_strType.data() << endl; 27 return m_strType; 28 } 29 private: 30 string m_strType; 31 }; 32 33 //具體的產品類 34 class Tank96 : public Tank 35 { 36 public: 37 Tank96():Tank(),m_strType("Tank96") 38 { 39 } 40 const string& type() override 41 { 42 cout << m_strType.data() << endl; 43 return m_strType; 44 } 45 46 private: 47 string m_strType; 48 }; 49 50 //抽象工廠類,提供一個創建接口 51 class TankFactory 52 { 53 public: 54 //提供創建產品實例的接口,返回抽象產品類 55 virtual Tank* createTank() = 0; 56 }; 57 58 //具體的創建工廠類,使用抽象工廠類提供的接口,去創建具體的產品實例 59 class Tank56Factory : public TankFactory 60 { 61 public: 62 Tank* createTank() override 63 { 64 return new Tank56(); 65 } 66 }; 67 68 //具體的創建工廠類,使用抽象工廠類提供的接口,去創建具體的產品實例 69 class Tank96Factory : public TankFactory 70 { 71 public: 72 Tank* createTank() override 73 { 74 return new Tank96(); 75 } 76 }; 77 78 79 int main() 80 { 81 TankFactory* factory56 = new Tank56Factory(); 82 Tank* tank56 = factory56->createTank(); 83 tank56->type(); 84 85 TankFactory* factory96 = new Tank96Factory(); 86 Tank* tank96 = factory96->createTank(); 87 tank96->type(); 88 89 delete tank96; 90 tank96 = nullptr; 91 delete factory96; 92 factory96 = nullptr; 93 94 delete tank56; 95 tank56 = nullptr; 96 delete factory56; 97 factory56 = nullptr; 98 99 return 0; 100 }
1.3、抽象工廠模式
抽象工廠模式提供創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。
當存在多個產品系列,而客戶端只使用一個系列的產品時,可以考慮使用抽象工廠模式。
缺點:當增加一個新系列的產品時,不僅需要現實具體的產品類,還需要增加一個新的創建接口,擴展相對困難。
1 /* 2 * 關鍵代碼:在一個工廠里聚合多個同類產品。 3 * 以下代碼以白色衣服和黑色衣服為例,白色衣服為一個產品系列,黑色衣服為一個產品系列。白色上衣搭配白色褲子, 黑色上衣搭配黑色褲字。每個系列的衣服由一個對應的工廠創建,這樣一個工廠創建的衣服能保證衣服為同一個系列。 4 */ 5 6 //抽象上衣類 7 class Coat 8 { 9 public: 10 virtual const string& color() = 0; 11 }; 12 13 //黑色上衣類 14 class BlackCoat : public Coat 15 { 16 public: 17 BlackCoat():Coat(),m_strColor("Black Coat") 18 { 19 } 20 21 const string& color() override 22 { 23 cout << m_strColor.data() << endl; 24 return m_strColor; 25 } 26 private: 27 string m_strColor; 28 }; 29 30 //白色上衣類 31 class WhiteCoat : public Coat 32 { 33 public: 34 WhiteCoat():Coat(),m_strColor("White Coat") 35 { 36 } 37 const string& color() override 38 { 39 cout << m_strColor.data() << endl; 40 return m_strColor; 41 } 42 43 private: 44 string m_strColor; 45 }; 46 47 //抽象褲子類 48 class Pants 49 { 50 public: 51 virtual const string& color() = 0; 52 }; 53 54 //黑色褲子類 55 class BlackPants : public Pants 56 { 57 public: 58 BlackPants():Pants(),m_strColor("Black Pants") 59 { 60 } 61 const string& color() override 62 { 63 cout << m_strColor.data() << endl; 64 return m_strColor; 65 } 66 67 private: 68 string m_strColor; 69 }; 70 71 //白色褲子類 72 class WhitePants : public Pants 73 { 74 public: 75 WhitePants():Pants(),m_strColor("White Pants") 76 { 77 } 78 const string& color() override 79 { 80 cout << m_strColor.data() << endl; 81 return m_strColor; 82 } 83 84 private: 85 string m_strColor; 86 }; 87 88 //抽象工廠類,提供衣服創建接口 89 class Factory 90 { 91 public: 92 //上衣創建接口,返回抽象上衣類 93 virtual Coat* createCoat() = 0; 94 //褲子創建接口,返回抽象褲子類 95 virtual Pants* createPants() = 0; 96 }; 97 98 //創建白色衣服的工廠類,具體實現創建白色上衣和白色褲子的接口 99 class WhiteFactory : public Factory 100 { 101 public: 102 Coat* createCoat() override 103 { 104 return new WhiteCoat(); 105 } 106 107 Pants* createPants() override 108 { 109 return new WhitePants(); 110 } 111 }; 112 113 //創建黑色衣服的工廠類,具體實現創建黑色上衣和白色褲子的接口 114 class BlackFactory : public Factory 115 { 116 Coat* createCoat() override 117 { 118 return new BlackCoat(); 119 } 120 121 Pants* createPants() override 122 { 123 return new BlackPants(); 124 } 125 };
2、策略模式
策略模式是指定義一系列的算法,把它們單獨封裝起來,並且使它們可以互相替換,使得算法可以獨立於使用它的客戶端而變化,也是說這些算法所完成的功能類型是一樣的,對外接口也是一樣的,只是不同的策略為引起環境角色環境角色表現出不同的行為。
相比於使用大量的if...else,使用策略模式可以降低復雜度,使得代碼更容易維護。
缺點:可能需要定義大量的策略類,並且這些策略類都要提供給客戶端。
2.1、傳統的策略模式實現
1 /* 2 * 關鍵代碼:實現同一個接口。 3 * 以下代碼實例中,以游戲角色不同的攻擊方式為不同的策略,游戲角色即為執行不同策略的環境角色。 4 */ 5 6 #include <iostream> 7 8 using namespace std; 9 10 //抽象策略類,提供一個接口 11 class Hurt 12 { 13 public: 14 virtual void blood() = 0; 15 }; 16 17 //具體的策略實現類,具體實現接口, Adc持續普通攻擊 18 class AdcHurt : public Hurt 19 { 20 public: 21 void blood() override 22 { 23 cout << "Adc hurt, Blood loss" << endl; 24 } 25 }; 26 27 //具體的策略實現類,具體實現接口, Apc技能攻擊 28 class ApcHurt : public Hurt 29 { 30 public: 31 void blood() override 32 { 33 cout << "Apc Hurt, Blood loss" << endl; 34 } 35 }; 36 37 //環境角色類, 游戲角色戰士,傳入一個策略類指針參數。 38 class Soldier 39 { 40 public: 41 Soldier(Hurt* hurt):m_pHurt(hurt) 42 { 43 } 44 //在不同的策略下,該游戲角色表現出不同的攻擊 45 void attack() 46 { 47 m_pHurt->blood(); 48 } 49 private: 50 Hurt* m_pHurt; 51 }; 52 53 //定義策略標簽 54 typedef enum 55 { 56 Hurt_Type_Adc, 57 Hurt_Type_Apc, 58 Hurt_Type_Num 59 }HurtType; 60 61 //環境角色類, 游戲角色法師,傳入一個策略標簽參數。 62 class Mage 63 { 64 public: 65 Mage(HurtType type) 66 { 67 switch(type) 68 { 69 case Hurt_Type_Adc: 70 m_pHurt = new AdcHurt(); 71 break; 72 case Hurt_Type_Apc: 73 m_pHurt = new ApcHurt(); 74 break; 75 default: 76 break; 77 } 78 } 79 ~Mage() 80 { 81 delete m_pHurt; 82 m_pHurt = nullptr; 83 cout << "~Mage()" << endl; 84 } 85 86 void attack() 87 { 88 m_pHurt->blood(); 89 } 90 private: 91 Hurt* m_pHurt; 92 }; 93 94 //環境角色類, 游戲角色弓箭手,實現模板傳遞策略。 95 template<typename T> 96 class Archer 97 { 98 public: 99 void attack() 100 { 101 m_hurt.blood(); 102 } 103 private: 104 T m_hurt; 105 }; 106 107 int main() 108 { 109 Archer<ApcHurt>* arc = new Archer<ApcHurt>; 110 arc->attack(); 111 112 delete arc; 113 arc = nullptr; 114 115 return 0; 116 }
2.2、使用函數指針實現策略模式
1 #include <iostream> 2 #include <functional> 3 4 void adcHurt() 5 { 6 std::cout << "Adc Hurt" << std::endl; 7 } 8 9 void apcHurt() 10 { 11 std::cout << "Apc Hurt" << std::endl; 12 } 13 14 //環境角色類, 使用傳統的函數指針 15 class Soldier 16 { 17 public: 18 typedef void (*Function)(); 19 Soldier(Function fun): m_fun(fun) 20 { 21 } 22 void attack() 23 { 24 m_fun(); 25 } 26 private: 27 Function m_fun; 28 }; 29 30 //環境角色類, 使用std::function<> 31 class Mage 32 { 33 public: 34 typedef std::function<void()> Function; 35 36 Mage(Function fun): m_fun(fun) 37 { 38 } 39 void attack() 40 { 41 m_fun(); 42 } 43 private: 44 Function m_fun; 45 }; 46 47 int main() 48 { 49 Soldier* soldier = new Soldier(apcHurt); 50 soldier->attack(); 51 delete soldier; 52 soldier = nullptr; 53 return 0; 54 }
3、適配器模式
適配器模式可以將一個類的接口轉換成客戶端希望的另一個接口,使得原來由於接口不兼容而不能在一起工作的那些類可以在一起工作。通俗的講就是當我們已經有了一些類,而這些類不能滿足新的需求,此時就可以考慮是否能將現有的類適配成可以滿足新需求的類。適配器類需要繼承或依賴已有的類,實現想要的目標接口。
缺點:過多地使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是 A 接口,其實內部被適配成了 B 接口的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
3.1、使用復合實現適配器模式
1 /* 2 * 關鍵代碼:適配器繼承或依賴已有的對象,實現想要的目標接口。 3 * 以下示例中,假設我們之前有了一個雙端隊列,新的需求要求使用棧和隊列來完成。 4 雙端隊列可以在頭尾刪減或增加元素。而棧是一種先進后出的數據結構,添加數據時添加到棧的頂部,刪除數據時先刪 除棧頂部的數據。因此我們完全可以將一個現有的雙端隊列適配成一個棧。 5 */ 6 7 //雙端隊列, 被適配類 8 class Deque 9 { 10 public: 11 void push_back(int x) 12 { 13 cout << "Deque push_back:" << x << endl; 14 } 15 void push_front(int x) 16 { 17 cout << "Deque push_front:" << x << endl; 18 } 19 void pop_back() 20 { 21 cout << "Deque pop_back" << endl; 22 } 23 void pop_front() 24 { 25 cout << "Deque pop_front" << endl; 26 } 27 }; 28 29 //順序類,抽象目標類 30 class Sequence 31 { 32 public: 33 virtual void push(int x) = 0; 34 virtual void pop() = 0; 35 }; 36 37 //棧,后進先出, 適配類 38 class Stack:public Sequence 39 { 40 public: 41 //將元素添加到堆棧的頂部。 42 void push(int x) override 43 { 44 m_deque.push_front(x); 45 } 46 //從堆棧中刪除頂部元素 47 void pop() override 48 { 49 m_deque.pop_front(); 50 } 51 private: 52 Deque m_deque; 53 }; 54 55 //隊列,先進先出,適配類 56 class Queue:public Sequence 57 { 58 public: 59 //將元素添加到隊列尾部 60 void push(int x) override 61 { 62 m_deque.push_back(x); 63 } 64 //從隊列中刪除頂部元素 65 void pop() override 66 { 67 m_deque.pop_front(); 68 } 69 private: 70 Deque m_deque; 71 };
3.2、使用繼承實現適配器模式
1 //雙端隊列,被適配類 2 class Deque 3 { 4 public: 5 void push_back(int x) 6 { 7 cout << "Deque push_back:" << x << endl; 8 } 9 void push_front(int x) 10 { 11 cout << "Deque push_front:" << x << endl; 12 } 13 void pop_back() 14 { 15 cout << "Deque pop_back" << endl; 16 } 17 void pop_front() 18 { 19 cout << "Deque pop_front" << endl; 20 } 21 }; 22 23 //順序類,抽象目標類 24 class Sequence 25 { 26 public: 27 virtual void push(int x) = 0; 28 virtual void pop() = 0; 29 }; 30 31 //棧,后進先出, 適配類 32 class Stack:public Sequence, private Deque 33 { 34 public: 35 void push(int x) 36 { 37 push_front(x); 38 } 39 void pop() 40 { 41 pop_front(); 42 } 43 }; 44 45 //隊列,先進先出,適配類 46 class Queue:public Sequence, private Deque 47 { 48 public: 49 void push(int x) 50 { 51 push_back(x); 52 } 53 void pop() 54 { 55 pop_front(); 56 } 57 };
4、單例模式
單例模式顧名思義,保證一個類僅可以有一個實例化對象,並且提供一個可以訪問它的全局接口。實現單例模式必須注意一下幾點:
-
單例類只能由一個實例化對象。
-
單例類必須自己提供一個實例化對象。
-
單例類必須提供一個可以訪問唯一實例化對象的接口。
單例模式分為懶漢和餓漢兩種實現方式。
4.1、懶漢單例模式
懶漢:故名思義,不到萬不得已就不會去實例化類,也就是說在第一次用到類實例的時候才會去實例化一個對象。在訪問量較小,甚至可能不會去訪問的情況下,采用懶漢實現,這是以時間換空間。
4.1.1、非線程安全的懶漢單例模式
1 /* 2 * 關鍵代碼:構造函數是私有的,不能通過賦值運算,拷貝構造等方式實例化對象。 3 */ 4 5 //懶漢式一般實現:非線程安全,getInstance返回的實例指針需要delete 6 class Singleton 7 { 8 public: 9 static Singleton* getInstance(); 10 ~Singleton(){} 11 12 private: 13 Singleton(){} //構造函數私有 14 Singleton(const Singleton& obj) = delete; //明確拒絕 15 Singleton& operator=(const Singleton& obj) = delete; //明確拒絕 16 17 static Singleton* m_pSingleton; 18 }; 19 20 Singleton* Singleton::m_pSingleton = NULL; 21 22 Singleton* Singleton::getInstance() 23 { 24 if(m_pSingleton == NULL) 25 { 26 m_pSingleton = new Singleton; 27 } 28 return m_pSingleton; 29 }
4.1.2、線程安全的懶漢單例模式
1 std::mutex mt; 2 3 class Singleton 4 { 5 public: 6 static Singleton* getInstance(); 7 private: 8 Singleton(){} //構造函數私有 9 Singleton(const Singleton&) = delete; //明確拒絕 10 Singleton& operator=(const Singleton&) = delete; //明確拒絕 11 12 static Singleton* m_pSingleton; 13 14 }; 15 Singleton* Singleton::m_pSingleton = NULL; 16 17 Singleton* Singleton::getInstance() 18 { 19 if(m_pSingleton == NULL) 20 { 21 mt.lock(); 22 if(m_pSingleton == NULL) 23 { 24 m_pSingleton = new Singleton(); 25 } 26 mt.unlock(); 27 } 28 return m_pSingleton; 29 }
4.1.3、返回一個reference指向local static對象
這種單例模式實現方式多線程可能存在不確定性:任何一種non-const static對象,不論它是local或non-local,在多線程環境下“等待某事發生”都會有麻煩。解決的方法:在程序的單線程啟動階段手工調用所有reference-returning函數。這種實現方式的好處是不需要去delete它。
1 class Singleton 2 { 3 public: 4 static Singleton& getInstance(); 5 private: 6 Singleton(){} 7 Singleton(const Singleton&) = delete; //明確拒絕 8 Singleton& operator=(const Singleton&) = delete; //明確拒絕 9 }; 10 11 12 Singleton& Singleton::getInstance() 13 { 14 static Singleton singleton; 15 return singleton; 16 }
4.2、餓漢單例模式
餓漢:餓了肯定要飢不擇食。所以在單例類定義的時候就進行實例化。在訪問量比較大,或者可能訪問的線程比較多時,采用餓漢實現,可以實現更好的性能。這是以空間換時間。
1 //餓漢式:線程安全,注意一定要在合適的地方去delete它 2 class Singleton 3 { 4 public: 5 static Singleton* getInstance(); 6 private: 7 Singleton(){} //構造函數私有 8 Singleton(const Singleton&) = delete; //明確拒絕 9 Singleton& operator=(const Singleton&) = delete; //明確拒絕 10 11 static Singleton* m_pSingleton; 12 }; 13 14 Singleton* Singleton::m_pSingleton = new Singleton(); 15 16 Singleton* Singleton::getInstance() 17 { 18 return m_pSingleton; 19 }
5、原型模式
原型模式:用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。通俗的講就是當需要創建一個新的實例化對象時,我們剛好有一個實例化對象,但是已經存在的實例化對象又不能直接使用。這種情況下拷貝一個現有的實例化對象來用,可能會更方便。
以下情形可以考慮使用原型模式:
-
當new一個對象,非常繁瑣復雜時,可以使用原型模式來進行復制一個對象。比如創建對象時,構造函數的參數很多,而自己又不完全的知道每個參數的意義,就可以使用原型模式來創建一個新的對象,不必去理會創建的過程。
-
當需要new一個新的對象,這個對象和現有的對象區別不大,我們就可以直接復制一個已有的對象,然后稍加修改。
-
當需要一個對象副本時,比如需要提供對象的數據,同時又需要避免外部對數據對象進行修改,那就拷貝一個對象副本供外部使用。
1 /* 2 * 關鍵代碼:拷貝,return new className(*this); 3 */ 4 #include <iostream> 5 6 using namespace std; 7 8 //提供一個抽象克隆基類。 9 class Clone 10 { 11 public: 12 virtual Clone* clone() = 0; 13 virtual void show() = 0; 14 }; 15 16 //具體的實現類 17 class Sheep:public Clone 18 { 19 public: 20 Sheep(int id, string name):Clone(), 21 m_id(id),m_name(name) 22 { 23 cout << "Sheep() id address:" << &m_id << endl; 24 cout << "Sheep() name address:" << &m_name << endl; 25 } 26 ~Sheep() 27 { 28 } 29 //關鍵代碼拷貝構造函數 30 Sheep(const Sheep& obj) 31 { 32 this->m_id = obj.m_id; 33 this->m_name = obj.m_name; 34 cout << "Sheep(const Sheep& obj) id address:" << &m_id << endl; 35 cout << "Sheep(const Sheep& obj) name address:" << &m_name << endl; 36 } 37 //關鍵代碼克隆函數,返回return new Sheep(*this) 38 Clone* clone() 39 { 40 return new Sheep(*this); 41 } 42 void show() 43 { 44 cout << "id :" << m_id << endl; 45 cout << "name:" << m_name.data() << endl; 46 } 47 private: 48 int m_id; 49 string m_name; 50 }; 51 52 int main() 53 { 54 Clone* s1 = new Sheep(1, "abs"); 55 s1->show(); 56 Clone* s2 = s1->clone(); 57 s2->show(); 58 59 delete s1; 60 s1 = nullptr; 61 delete s2; 62 s2 = nullptr; 63 return 0; 64 }