常用設計模式C++示例1:建立型模式
PS:關於設計模式,推薦參考電子書:設計模式。本部落格程式碼均參考以上電子書,僅供查閱,不便於學習。
1. 單例模式
1.1 概念及實現方法
單例模式保證一個類只存在一個例項,同時提供訪問該例項的全域性節點,該全域性節點僅在首次訪問的時候初始化。為了實現單例模式,需要做以下操作:
(1) 建構函式均宣告為private,這樣外部不能進行單例類初始化;
(2) 提供全域性訪問節點,private的靜態類指標,這樣所有類只有一個指標;
(3) 提供靜態成員方法getInstance(),該方法在首次訪問全域性節點時初始化,以後再訪問時直接返回已初始化的全域性訪問節點。
1.2 C++實現
非執行緒安全實現:
class Singleton { private: Singleton(string s):m_value(s) {}; //private的建構函式 static Singleton* m_instance; //靜態成員指標 string m_value; public: Singleton() = delete; //除了getInstance能獲取例項,其他建構函式一律禁止 Singleton(Singleton& single) = delete; Singleton operator=(Singleton& single) = delete; static Singleton* getInstance(string s) //訪問全域性節點函式,獲得該節點的指標 { if (m_instance == NULL) m_instance = new Singleton(s); return m_instance; } string value() const { return m_value; } };
Singleton* Singleton::m_instance = nullptr; //靜態成員變數只能在類外初始化
因為該類在getInstance()成員函式中沒有對m_instance(相當於全域性變數)加鎖,所以該類是非執行緒安全的。通過下面程式碼來驗證:
1 void ThreadBar() 2 { 3 //std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 4 Singleton* instance = Singleton::getInstance("Bar"); 5 cout << instance->value() << "\n"; 6 } 7 8 void ThreadFoo() 9 { 10 //std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 11 Singleton* instance = Singleton::getInstance("Foo"); 12 cout << instance->value() << "\n"; 13 } 14 15 16 int main() 17 { 18 thread th1(ThreadBar); 19 thread th2(ThreadFoo); 20 th1.join(); 21 th2.join(); 22 23 return 0; 24 }View Code
上面的程式碼通過兩個執行緒去初始化例項,執行時,會出現“Foo"和”Bar"同時列印,說明進行了兩次例項化。為了修改為執行緒安全,在getInstance函式中需要新增鎖。修改後的getInstance()如下:
class Singleton { private: static mutex m_mutex; //新增鎖 public: static Singleton* getInstance(string s) { if (m_instance == NULL) { lock_guard<mutex> lcok(m_mutex); //上鎖 if(m_instance==NULL) m_instance = new Singleton(s); } return m_instance; } }
PS:關於簡單工廠模式,工廠模式,抽象工廠模式的區別,可以參考簡單工廠模式,工廠模式,抽象工廠模式,也可以參考本部落格第四節。
2. 工廠模式
2.1 工廠模式概念及實現要點
工廠模式就是設計一個公共的介面和對應的操作函式。將具體的各個類用抽基類指標或引用表達,具體的操作函式用基類的函式去表達,相當於基類成為具體類的一個介面。所有對具體類的操作都通過基類實現。一個工廠模式有四大要素:
(1) product(所有產品類的抽象類,包含對應的操作函式介面),一般為純虛類;
(2 )concreteProduct(繼承product,實現具體的操作函式),為實際產品類;
(3) creator,宣告建立函式,能建立不同的concreteProduct,同時宣告操作介面,能呼叫concreteProduct對應的操作介面,一般為純虛類;
(4) concreteCreator,建立一個具體concreteProduct的類。
可以看到,以上四要素有兩個純虛基類,用於宣告介面。兩個字類,concreteCreator用於建立具體的concreteProduct。
對應的一個關係如下圖所示:
2.2 C++實現
首先需要實現四個類:
1 //產品類,宣告不同的產品介面,純虛類 2 class product { 3 public: 4 virtual ~product() {}; 5 virtual string operation() const = 0; //操作介面 6 }; 7 8 //具體產品類,實現相同的操作介面 9 class detailedProduct1:public product 10 { 11 public: 12 //實現具體的產品介面 13 string operation()const override { 14 return "operation of detailedProduct 1"; 15 } 16 }; 17 18 class detailedProduct2 :public product 19 { 20 public: 21 //實現具體的產品介面 22 string operation()const override { 23 return "operation of detailedProduct 2"; 24 } 25 }; 26 27 //生產者類,主要宣告兩種介面: 28 //1. 宣告建立函式,根據引數型別,建立不同的product 29 //2. 宣告通用的操作介面,根據建立的product呼叫對應的操作介面 30 class creator { 31 public: 32 virtual ~creator() {}; 33 virtual product* factoryMethod() const = 0; 34 void operation() { 35 product* pro = factoryMethod(); 36 cout << pro->operation() << endl; 37 delete pro; 38 } 39 }; 40 41 //具體的建立者類,建立具體的product 42 class product1Creator:public creator 43 { 44 public: 45 product* factoryMethod() const override { 46 return new detailedProduct1(); 47 } 48 }; 49 50 class product2Creator :public creator 51 { 52 public: 53 product* factoryMethod() const override { 54 return new detailedProduct2(); 55 } 56 };
在使用時,利用基類creator指標指向具體的子類creator建立的物件。然後就能呼叫子類product的方法介面,整個過程只有一個基類creator指標被建立。可以看到,工廠方法的好處在於:
- 當新的子類出現,只需要實現一個繼承自product基類的子類product;再實現一個繼承自creator基類的子類creator,對應具體的子類prodcut建立,就能呼叫相關product的操作函式。整個過程不需要修改原始碼,只需要新增新的程式碼即可。
下面看如何呼叫工廠模式。
int main() { creator* a1 = new product1Creator(); a1->operation(); creator* a2 = new product2Creator(); a2->operation(); delete a1, a2; return 0; }
3. 抽象工廠模式
3.1 概念及實現要點
抽象工廠模式的特點有:
(1) 存在多個抽象產品,純虛類,用於宣告產品的公有屬性和介面;
(2) 每個抽象產品有多個具象產品,實現具體的產品介面;
(3) 一個抽象工廠,生產抽象產品,純虛類,宣告生產產品的介面;
(4) 多個具象工廠,一個具象工廠生產每一種抽象產品的一個具象產品,因此一個抽象產品有多少具象產品,通常就有多少個具象工廠。
對應的關係如下圖:
抽象工廠具有如下特點:
(1) 同一工廠生產的產品是不同種類,性質相似的;
(2) 避免了客戶端和具體程式碼耦合;
(3) 單一職責原則,產品生成程式碼都在具象工廠內,方便修改;
(4) 開閉原則,在引入新的產品時,應用程式/客戶端無需修改程式。
3.2 C++程式碼實現
3.1中已經說明,實現抽象工廠需要四種類:抽象產品類,具象產品類,抽象工廠類,具象工廠類。
1 /*********************************** 2 定義抽象產品類A 3 1. 存在多個抽象產品,比如抽象產品:椅子,桌子,床 4 2. 一個抽象產品可以對應多個具象產品,比如抽象產品椅子,可以對應:有靠背的椅子,躺椅,無靠背椅子 5 但都具有相同的介面 6 ***********************************/ 7 class abstractProductA 8 { 9 public: 10 virtual ~abstractProductA() {}; 11 virtual string functionA() const = 0; 12 }; 13 14 //具象產品A1,實現抽象產品A的初始化的具象函式介面 15 class concreteProductA1:public abstractProductA 16 { 17 public: 18 string functionA() const override 19 { 20 return "Concrete product A1"; 21 } 22 }; 23 24 //具象產品A2,實現抽象產品A的初始化的具象函式介面 25 class concreteProductA2 :public abstractProductA 26 { 27 public: 28 string functionA() const override 29 { 30 return "Concrete product A2"; 31 } 32 }; 33 34 //抽象產品B 35 class abstractProductB { 36 public: 37 virtual ~abstractProductB() {}; 38 virtual string functionB() const = 0; 39 }; 40 41 //具象產品B1 42 class concreteProductB1 :public abstractProductB 43 { 44 public: 45 string functionB() const override { 46 return "Concrete product B1"; 47 } 48 }; 49 50 //具象產品B2 51 class concreteProductB2 :public abstractProductB 52 { 53 public: 54 string functionB() const override { 55 return "Concrete product B2"; 56 } 57 };
然後是工廠類,工廠類的抽象工廠負責定義介面並與客戶端程式碼互動,具象工廠負責具體產品生產:
1 /**************************************** 2 以上聲明瞭產品,還需要對應的工廠去生成產品,工廠也分兩類,抽象工廠和具象工廠 3 抽象工廠負責宣告統一的建立介面,建立不同的抽象產品,比如此處聲明瞭A,B兩類抽象產品 4 *****************************************/ 5 class abstractFactory 6 { 7 public: 8 virtual abstractProductA* createProductA() const = 0; 9 virtual abstractProductB* createProductB() const = 0; 10 }; 11 12 /******************************************** 13 具象工廠,專門生產具體的產品類,通常每個抽象產品類的一個具象產品類組成一組,由一個具象工廠生產 14 比如:此處有抽象產品A,B ,對應具象產品:A1,A2;B1,B2 15 那麼有多少種具象產品類,就有多少種具象工廠,每一個具象工廠負責生成所有抽象產品的一個具象產品 16 比如:此處A1,B1為1組,由一個具象工廠生產;A2,B2為一組 17 **********************************************/ 18 class concreteFactory1 :public abstractFactory 19 { 20 public: 21 abstractProductA* createProductA() const override { 22 return new concreteProductA1(); 23 } 24 abstractProductB* createProductB() const override { 25 return new concreteProductB1(); 26 } 27 }; 28 29 class concreteFactory2 :public abstractFactory 30 { 31 public: 32 abstractProductA* createProductA() const override { 33 return new concreteProductA2(); 34 } 35 abstractProductB* createProductB() const override { 36 return new concreteProductB2(); 37 } 38 };
接下來是客戶端程式碼,客戶端程式碼提供的引數輸入就是一個抽象工廠類,由於該類是純虛類,需要通過引用或指標指向具象工廠才能傳入實現多型。
1 /*********************************** 2 客戶端程式碼,只與抽象工廠打交道,不跟具象工廠耦合 3 根據傳進來的工廠物件的不同,建立不同的產品,實現不同的產品函式 4 ************************************/ 5 void clientCode(const abstractFactory& factory) 6 { 7 abstractProductA* prodcut_a = factory.createProductA(); 8 abstractProductB* product_b = factory.createProductB(); 9 cout << prodcut_a->functionA() << endl; 10 cout << product_b->functionB() << endl; 11 delete prodcut_a; 12 delete product_b; 13 } 14 15 16 17 int main() 18 { 19 concreteFactory1* factory1 = new concreteFactory1(); //建立具象工廠 20 clientCode(*factory1); 21 delete factory1; 22 return 0; 23 24 }View Code
4. 簡單工廠,工廠和抽象工廠的區別
以加減乘除算術運算為例,那麼此處抽象產品就是算術運算類,命名為arithmeticMethod,具象產品就是addMethod,minusMehod,multiMethod,divideMethod,分別為加減乘除類,那麼四種具象類分別實現一個方法getResult,用於實現加減乘除。
4.1 簡單工廠
簡單工廠一般只有三個類:抽象產品類,具象產品類,工廠類。工廠類中的createProduct方法包含邏輯控制語句,根據不同的swith分支建立不同的具象產品。下面看加減乘除算術運算的一個具體實現方式:
1 //抽象產品類 2 class arithmeticMethod 3 {public: 4 virtual ~arithmeticMethod() {}; 5 virtual double getResult(double a1, double a2) const = 0; 6 }; 7 8 //具象產品類 9 class addMethod:public arithmeticMethod 10 { 11 public: 12 double getResult(double a1, double a2) const override 13 { 14 return a1 + a2; 15 } 16 }; 17 18 //具象產品類 19 class minusMethod :public arithmeticMethod 20 { 21 public: 22 double getResult(double a1, double a2) const override 23 { 24 return a1 - a2; 25 } 26 }; 27 28 //具象產品類 29 class multiMethod :public arithmeticMethod 30 { 31 public: 32 double getResult(double a1, double a2) const override 33 { 34 return a1*a2; 35 } 36 }; 37 38 //具象產品類 39 class divideMethod :public arithmeticMethod 40 { 41 public: 42 double getResult(double a1, double a2) const override 43 { 44 return a1 / a2; //此處不進行型別檢測,僅對工廠系列方法進行講解 45 } 46 }; 47 48 class factory { 49 public: 50 arithmeticMethod* createProduct(char c) 51 { 52 switch (c) { 53 case '+':return new addMethod(); break; 54 case '-':return new minusMethod(); break; 55 case '*':return new multiMethod(); break; 56 case '/':return new divideMethod(); break; 57 } 58 } 59 }; 60 61 //客戶端程式碼 62 void clientCode(factory& f) //抽象產品指標 63 { 64 arithmeticMethod* product = f.createProduct('+'); 65 double res=product->getResult(1.2, 2.4); 66 cout << res << endl; 67 delete product; 68 } 69 70 71 int main() 72 { 73 74 factory* f = new factory(); 75 clientCode(*f); 76 return 0; 77 78 }
簡單工廠類所有具象產品的生產均在簡單工廠內部,當產品增加時,需要直接修改工廠程式碼,同時隨著產品增加簡單工廠的函式內容也會更復雜。
4.2 工廠模式
工廠模式就是把簡單工廠進行擴充套件,分為抽象工廠(只定義生成介面),具象工廠(將簡單工廠的switch分支實現為一個具象工廠),這樣新增新的類時,只需要新增一個具象工廠類。將4.1的程式碼修改為工廠模式:
1 /*********************************** 2 加減乘除的具象產品與4.1同理,只需要將簡單工廠擴充為一個純虛類工廠和多個具象工廠 3 ************************************/ 4 5 class factory { 6 public: 7 virtual ~factory() {}; 8 virtual arithmeticMethod* createProduct() const = 0; 9 }; 10 11 class addFactory:public factory 12 { 13 public: 14 arithmeticMethod* createProduct() const override { 15 return new addMethod(); 16 } 17 }; 18 19 class minusFactory :public factory 20 { 21 public: 22 arithmeticMethod* createProduct() const override { 23 return new minusMethod(); 24 } 25 }; 26 27 //乘除具象工廠同理,不再贅述 28 29 30 //客戶端程式碼 31 void clientCode(factory& f) //抽象產品指標 32 { 33 arithmeticMethod* product = f.createProduct(); 34 double res=product->getResult(1.2, 2.4); 35 cout << res << endl; 36 delete product; 37 } 38 39 40 int main() 41 { 42 43 factory* f = new addFactory(); 44 clientCode(*f); 45 return 0; 46 47 }
4.3 抽象工廠
簡單工廠和工廠模式均只能生成一種抽象產品,該抽象產品具有多個具象產品。但是當存在多個抽象產品,每個抽象產品又存在多個具象產品時,簡單工廠模式和工廠模式就有些捉襟見肘,抽象工廠模式相對於前兩種模式的區別就在於:
(1) 抽象工廠模式有多個抽象產品類,每個抽象產品有多個具象產品;
(2) 每個具象工廠不再只生產一種產品類,而是生產每個抽象產品的一個具象產品(同一工廠生產的一組具象產品具有同類共同屬性)。
4.1,4.2的例子用工廠模式就能很好解決了,再舉一個用於抽象工廠的例子(此例子為設計模式-抽象工廠模式中的例子)。假如現在工廠要生產椅子,桌子,沙發三個抽象產品,同時每個抽象產品又有不同風格,比如歐美風,中國風,非洲風。那麼相當於有三個抽象產品,每個抽象產品有三個具象產品,因此宣告一個抽象工廠,三個具象工廠,每個具象工廠生產一類風格的產品,比如工廠1只生成中國風的椅子,桌子和沙發(即同一具象工廠內生產的產品來自不同抽象產品類,但是相互間又有一致屬性)。程式碼再次不再詳述。
5. 生成器模式
5.1 概念及實現要點
在實際程式設計時,常常遇到某個類有很多屬性,在建構函式初始化時,為了類屬性初始化,建構函式的引數列表引數非常多。造成呼叫麻煩。這個時候,可以將建構函式拆分成幾個函式,每個函式初始化一部分引數。舉個具體的例子,比如建立一個房屋類,該房屋主要由牆,窗戶,門,傢俱,裝飾等屬性組成,可以用一系列的生成器,每個生成器呼叫不同的函式,生成不同型別的房屋。如下圖所示。
從上面的例子,也可以看出來一個生成器模式需要哪些基本元素:抽象產品類,抽象生成器類(純虛類),具象生成器類(多個具象生成器)。除此之外,通常還有一個主管(Director),主管傳入一個抽象生成器類,呼叫生成函式,生成最經典的產品(相當於量產產品)。這樣客戶端程式碼,就直接跟Director耦合,不必一次呼叫眾多的屬性生成函式。各個部件的結構如下圖所示。
5.2 C++實現
按照產品類,抽象生成器類,具象生成器類,主管類,客戶端程式碼五個部分,編寫的c++函式如下:
//生成器模式,通過一系列步驟完成初始化,即將建構函式拆分 //產品類 class Product { public: vector<string> m_parts; //所有屬性 void listParts() const { for (int i = 0; i < m_parts.size(); i++) { cout << m_parts[i] << " "; } cout << endl; } }; //抽象生成器,定義公有的特性新增介面 class Builder { public: virtual ~Builder() {}; virtual void producePartA() const = 0; virtual void producePartB() const = 0; virtual void producePartC() const = 0; }; //具象的生成器1,生成一類特定產品 class ConcreteBuilder1:public Builder { private: Product* prod; public: ConcreteBuilder1() { reset(); } void reset() { prod = new Product(); //生成產品基本型別 } ~ConcreteBuilder1() { delete prod; } //生成產品其他具體部件 void producePartA() const override { prod->m_parts.push_back("partA1"); } void producePartB() const override { prod->m_parts.push_back("partB1"); } void producePartC() const override { prod->m_parts.push_back("partC1"); } //對於某些產品,其除了公有的部件(生產步驟和生產函式),還有其獨有的部件,此種生產函式直接放到具體生成器中 void produceSpecialPart() { prod->m_parts.push_back("specialPart"); } //注:此處返回已經建造完成的產品,此後builder可以再用於建造其他產品 //注意刪除使用完成的產品 Product* getProduct() { Product* p = prod; reset(); return p; } }; //具體生成器2,定義的內容和步驟與生成器1相似 //管理者,主要負責通用的產品生產,就呼叫concreteBuilder的部件生產函式,實現特定流行產品生產 class Director { private: Builder* builder; public: void setBuilder(Builder* b) { builder = b; } void buildPrevalentProduct() { builder->producePartB(); builder->producePartA(); builder->producePartC(); } }; //客戶端程式碼 void clientCode(Director& d) { ConcreteBuilder1* b = new ConcreteBuilder1(); d.setBuilder(b); d.buildPrevalentProduct(); Product* p = b->getProduct(); p->listParts(); delete p; delete b; } int main() { Director* d = new Director(); clientCode(*d); return 0; }
需要注意的是ConcreteBuilder(具象生成器類)的getProduct()函式,該函式返回已經呼叫Director生產完成的產品指標,同時生成一個新的產品指標,因此用完返回的產品需要人為釋放空間。
6. 原型模式
6.1 概念及實現要點
當有一個類物件,希望對其進行復制時,常用的方法是遍歷原物件類的成員變數,並將其複製到新的物件中(這通常可以用拷貝建構函式完成)。但是這使得客戶端程式碼直接跟具體的類打交道,造成耦合關係。原型模式就是為了解決類複製問題引入的,其主要的結構和對應需要實現的類如下圖:
可以看到,實現一個原型模式,主要有抽象原型,具體原型,原型登錄檔三大類。抽象原型主要定義clone介面,可以複製一個具體原型類;具體原型實現介面;原型登錄檔將所有可克隆的原型組織成一個HashTable的形式,直接通過原型登錄檔的具體原型物件,呼叫其複製介面獲得新的具體原型物件。客戶端也只跟原型登錄檔互動。
6.2 C++實現
//在此給每個具體原型一個編號,宣告原型相關資訊,客戶端通過這個原型編號建立具體的原型 enum Type { PROTOTYPE1 = 0, PROTOTYPE2 }; //抽象原型,最主要的是clone介面 class Prototype { protected: string m_name; int m_val; public: Prototype(string name, int val) :m_name(name), m_val(val) {}; virtual Prototype* clone() const = 0; //克隆函式介面 virtual void showMems() const = 0; }; //具象原型1 class ConcretePrototype1:public Prototype { private: float m_score; //宣告其他的一些屬性 public: ConcretePrototype1(string name, int val, float score) :Prototype(name, val), m_score(score) {}; //注意克隆方法返回的是一個克隆物件,所以客戶端程式碼要負責刪除記憶體 Prototype* clone() const override { return new ConcretePrototype1(*this); } void showMems() const override { cout << this->m_name << " " << this->m_val << " " << this->m_score << endl; } }; //具象原型2 class ConcretePrototype2 :public Prototype { private: char m_ch; //宣告其他的一些屬性 public: ConcretePrototype2(string name, int val, char ch) :Prototype(name, val), m_ch(ch) {}; //注意克隆方法返回的是一個克隆物件,所以客戶端程式碼要負責刪除記憶體 Prototype* clone() const override { return new ConcretePrototype2(*this); } void showMems() const override { cout << this->m_name << " " << this->m_val << " " << this->m_ch << endl; } }; //原型工廠,即原型註冊器,通常包含所有可克隆的具體原型物件 class PrototypeFactory { private: unordered_map<int, Prototype*> m_prototypes; public: //在建立原型工廠時,生成所有可克隆物件,之後客戶端直接呼叫可克隆物件的clone方法獲得新的物件 PrototypeFactory() { m_prototypes[PROTOTYPE1] = new ConcretePrototype1("Prototype1",1,3.5); m_prototypes[PROTOTYPE2] = new ConcretePrototype2("Prototype2", 2, 'h'); } //釋放對應的記憶體 ~PrototypeFactory() { for (auto mem : m_prototypes) delete mem.second; } //呼叫此函式獲得一個具體原型物件,傳入type控制輸出型別 Prototype* createPrototype(int type) { return m_prototypes[type]->clone(); } }; //客戶端程式碼 void client(PrototypeFactory& factory) { Prototype* type1 = factory.createPrototype(Type::PROTOTYPE1); type1->showMems(); delete type1; } int main() { PrototypeFactory* factory = new PrototypeFactory(); client(*factory); delete factory; return 0; }