1. 程式人生 > 實用技巧 >常用設計模式C++示例1:建立型模式

常用設計模式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;
}