1. 程式人生 > >7、【C++】單例模式/工廠模式

7、【C++】單例模式/工廠模式

一、單例模式

    單例模式,可以說設計模式中最常應用的一種模式了,據說也是面試官最喜歡的題目。但是如果沒有學過設計模式的人,可能不會想到要去應用單例模式,面對單例模式適用的情況,可能會優先考慮使用全域性或者靜態變數的方式,這樣比較簡單,也是沒學過設計模式的人所能想到的最簡單的方式了。

    一般情況下,我們建立的一些類是屬於工具性質的,基本不用儲存太多的跟自身有關的資料,在這種情況下,每次都去new一個物件,即增加了開銷,也使得程式碼更加臃腫。其實,我們只需要一個例項物件就可以。如果採用全域性或者靜態變數的方式,會影響封裝性,難以保證別的程式碼不會對全域性變數造成影響。

    考慮到這些需要,我們將預設的建構函式宣告為私有的,這樣就不會被外部所new了,甚至可以將解構函式也宣告為私有的,這樣就只有自己能夠刪除自己了。在Java和C#這樣純的面向物件的語言中,單例模式非常好實現,直接就可以在靜態區初始化instance,然後通過getInstance返回,這種就被稱為餓漢式單例類。也有些寫法是在getInstance中new instance然後返回,這種就被稱為懶漢式單例類,但這涉及到第一次getInstance的一個判斷問題。

單執行緒中

Singleton* getInstance()
{
    if (instance == NULL)
//餓漢式單例模式(直接在getInstance函式中new一個instance,然後返回) instance = new Singleton();//懶漢式單例模式 return instance; }

    這樣就可以了,保證只取得了一個例項。但是在多執行緒的環境下卻不行了,因為很可能兩個執行緒同時執行到if (instance == NULL)這一句,導致可能會產生兩個例項。於是就要在程式碼中加鎖。

Singleton* getInstance()
{
    lock();
    if (instance == NULL)
    {
instance = new Singleton(); } unlock(); return instance; }

    但這樣寫的話,會稍稍映像效能,因為每次判斷是否為空都需要被鎖定,如果有很多執行緒的話,就愛會造成大量執行緒的阻塞。於是大神們又想出了雙重鎖定。

Singleton* getInstance()
{
    if (instance == NULL)
    {
    lock();
        if (instance == NULL)
        {
               instance = new Singleton();
        }
        unlock();
    }

    return instance;
}

    這樣只夠極低的機率下,通過越過了if (instance == NULL)的執行緒才會有進入鎖定臨界區的可能性,這種機率還是比較低的,不會阻塞太多的執行緒,但為了防止一個執行緒進入臨界區建立例項,另外的執行緒也進去臨界區建立例項,又加上了一道防禦if (instance == NULL),這樣就確保不會重複建立了。

常用的場景

    單例模式常常與工廠模式結合使用,因為工廠只需要建立產品例項就可以了,在多執行緒的環境下也不會造成任何的衝突,因此只需要一個工廠例項就可以了。
優點
    1.減少了時間和空間的開銷(new例項的開銷)。
    2.提高了封裝性,使得外部不易改動例項。
缺點
    1.懶漢式是以時間換空間的方式。(在getInstance中new instance然後返回)
    2.餓漢式是以空間換時間的方式。(在靜態區初始化instance,然後通過getInstance返回)

【示例】餓漢單例模式

//Singleton.h
#ifndef _SINGLETON_H_
#define _SINGLETON_H_

class Singleton{
public:
    static Singleton* getInstance();//宣告靜態成員函式

private:
    Singleton();
    //把複製建構函式和=操作符也設為私有,防止被複制
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    //在靜態區定義並初始化例項
    static Singleton* instance;//宣告靜態成員變數
};

#endif
//Singleton.cpp
#include "Singleton.h"

Singleton::Singleton(){

}

Singleton::Singleton(const Singleton&){

}

Singleton& Singleton::operator=(const Singleton&){

}

//定義並初始化靜態成員變數
Singleton* Singleton::instance = new Singleton();
//定義靜態成員函式
Singleton* Singleton::getInstance(){
    return instance;
}
//main.cpp
#include "Singleton.h"
#include <stdio.h>

int main(){
    Singleton* singleton1 = Singleton::getInstance();
    Singleton* singleton2 = Singleton::getInstance();

    if (singleton1 == singleton2)
        fprintf(stderr,"singleton1 = singleton2\n");

    return 0;
}

    執行結果:

    singleton1 = singleton2

二、工廠模式

    C++的工廠模式分為三種:簡單工廠模式、工廠模式和抽象工廠模式

一、簡單工廠模式

    簡單工廠模式是工廠模式中最簡單的一種,他可以用比較簡單的方式隱藏建立物件的細節,一般只需要告訴工廠類所需要的型別,工廠類就會返回需要的產品類,但客戶端看到的只是產品的抽象物件,無需關心到底是返回了哪個子類。客戶端唯一需要知道的具體子類就是工廠子類。除了這點,基本是達到了依賴倒轉原則的要求。

    假如,我們不用工廠類,只用AbstractProduct和它的子類,那客戶端每次使用不同的子類的時候都需要知道到底是用哪一個子類,當類比較少的時候還沒什麼問題,但是當類比較多的時候,管理起來就非常的麻煩了,就必須要做大量的替換,一個不小心就會發生錯誤。

    而使用了工廠類之後,就不會有這樣的問題,不管裡面多少個類,我只需要知道型別號即可。不過,這裡還有一個疑問,那就是如果我每次用工廠類建立的型別都不相同,這樣修改起來的時候還是會出現問題,還是需要大量的替換。所以簡單工廠模式一般應該於程式中大部分地方都只使用其中一種產品,工廠類也不用頻繁建立產品類的情況。這樣修改的時候只需要修改有限的幾個地方即可。

常用的場景

    例如部署多種資料庫的情況,可能在不同的地方要使用不同的資料庫,此時只需要在配置檔案中設定資料庫的型別,每次再根據型別生成例項,這樣,不管下面的資料庫型別怎麼變化,在客戶端看來都是隻有一個AbstractProduct,使用的時候根本無需修改程式碼。提供的型別也可以用比較便於識別的字串,這樣不用記很長的類名,還可以儲存為配置檔案。

    這樣,每次只需要修改配置檔案和新增新的產品子類即可。

    所以簡單工廠模式一般應用於多種同類型類的情況,將這些類隱藏起來,再提供統一的介面,便於維護和修改。

優點

    1.隱藏了物件建立的細節,將產品的例項化推遲到子類中實現。

    2.客戶端基本不用關心使用的是哪個產品,只需要知道用哪個工廠就行了,提供的型別也可以用比較便於識別的字串。

    3.方便新增新的產品子類,每次只需要修改工廠類傳遞的型別值就行了。

    4.遵循了依賴倒轉原則。

缺點

    1.要求產品子類的型別差不多,使用的方法名都相同,如果類比較多,而所有的類又必須要新增一種方法,則會是非常麻煩的事情。或者是一種類另一種類有幾種方法不相同,客戶端無法知道是哪一個產品子類,也就無法呼叫這幾個不相同的方法。

    2.每新增一個產品子類,都必須在工廠類中新增一個判斷分支,這違背了開放-封閉原則。

【示例】

//AbstractProduct.h
#ifndef _ABSTRACTPRODUCT_H_
#define _ABSTRACTPRODUCT_H_

#include <stdio.h>
//抽象類
class AbstractProduct{
public:
    AbstractProduct();
    virtual ~AbstractProduct();
    
public:
    virtual void operation() = 0;
};

class ProductA:public AbstractProduct{
public:
    ProductA();
    virtual ~ProductA();
    
public:
    void operation();
};

class ProductB:public AbstractProduct{
public:
    ProductB();
    ~ProductB();
    
public:
    void operation();
};

#endif
//AbstractProduct.cpp
#include "AbstractProduct.h"

AbstractProduct::AbstractProduct(){
}
AbstractProduct::~AbstractProduct(){
}

ProductA::ProductA(){
}
ProductA::~ProductA(){
}
void ProductA::operation(){
    fprintf(stderr,"productA operation!\n");
}

ProductB::ProductB(){
}
ProductB::~ProductB(){
}
void ProductB::operation(){
    fprintf(stderr,"productB operation!\n");
}
//SimpleFactory.h
#ifndef _SIMPLEFACTORY_H_
#define _SIMPLEFACTROY_H_

#include <stdio.h>
#include "AbstractProduct.h"

class AbstractFactory{
public:
    AbstractFactory();
    virtual ~AbstractFactory();
    
public:
    virtual AbstractProduct* createProduct(int type) = 0;    
};

class SimpleFactory:public AbstractFactory{
public:
    SimpleFactory();
    ~SimpleFactory();
    
public:
    AbstractProduct* createProduct(int type);
};

#endif
#include "SimpleFactory.h"

AbstractFactory::AbstractFactory(){
}
AbstractFactory::~AbstractFactory(){
}

SimpleFactory::SimpleFactory(){
}
SimpleFactory::~SimpleFactory(){
}

AbstractProduct* SimpleFactory::createProduct(int type){
    AbstractProduct* temp = NULL;
    switch(type)
    {
    case 1:
        temp = new ProductA();
        break;
    case 2:
        temp = new ProductB();
        break;
    default:
        break;
    }
    return temp;
}
//client.cpp
#include "SimpleFactory.h"

int main(){
    AbstractFactory* factory = new SimpleFactory();
    AbstractProduct* product = factory->createProduct(1);
    product->operation();
    delete product;
    product = NULL;
    
    product = factory->createProduct(2);
    product->operation();
    delete product;
    product = NULL;
    return 0;
}
二、工廠模式

    工廠模式基本與簡單工廠模式差不多,上面也說了,每次新增一個產品子類都必須在工廠類中新增一個判斷分支,這樣違背了開放-封閉原則,因此,工廠模式就是為了解決這個問題而產生的。

    既然每次都要判斷,那我就把這些判斷都生成一個工廠子類,這樣,每次新增產品子類的時候,只需再新增一個工廠子類就可以了。這樣就完美的遵循了開放-封閉原則。但這其實也有問題,如果產品數量足夠多,要維護的量就會增加,好在一般工廠子類只用來生成產品類,只要產品子類的名稱不發生變化,那麼基本工廠子類就不需要修改,每次只需要修改產品子類就可以了。

    同樣工廠模式一般應該於程式中大部分地方都只使用其中一種產品,工廠類也不用頻繁建立產品類的情況。這樣修改的時候只需要修改有限的幾個地方即可。

常用的場景

    基本與簡單工廠模式一致,只不過是改進了簡單工廠模式中的開放-封閉原則的缺陷,使得模式更具有彈性。將例項化的過程推遲到子類中,由子類來決定例項化哪個。

優點

    基本與簡單工廠模式一致,多的一點優點就是遵循了開放-封閉原則,使得模式的靈活性更強
缺點

    與簡單工廠模式差不多。
【示例】

//AbstractProduct.h
#ifndef _ABSTRACTPRODUCT_H_
#define _ABSTRACTPRODUCT_H_

#include <stdio.h>

class AbstractProduct{
public:
    AbstractProduct();
    virtual ~AbstractProduct();
    
public:
    virtual void operation() = 0;
};

class ProductA:public AbstractProduct{
public:
    ProductA();
    virtual ~ProductA();
    
public:
    void operation();
};

class ProductB:public AbstractProduct{
public:
    ProductB();
    ~ProductB();
    
public:
    void operation();
};

#endif
//AbstractProduct.h
#include "AbstractProduct.h"

AbstractProduct::AbstractProduct(){
}
AbstractProduct::~AbstractProduct(){
}

ProductA::ProductA(){
}
ProductA::~ProductA(){
}
void ProductA::operation(){
    fprintf(stderr,"productA operation!\n");
}

ProductB::ProductB(){
}
ProductB::~ProductB(){
}
void ProductB::operation(){
    fprintf(stderr,"productB operation!\n");
}
//AbstractFactory.h
#ifndef _SIMPLEFACTORY_H_
#define _SIMPLEFACTROY_H_

#include <stdio.h>
#include "AbstractProduct.h"


class AbstractFactory{

public:
    AbstractFactory();
    virtual ~AbstractFactory();
    
public:
    virtual AbstractProduct* createProduct() = 0;    
};


class FactoryA:public AbstractFactory{

public:
    FactoryA();
    ~FactoryA();
    
public:
    AbstractProduct* createProduct();
};


class FactoryB:public AbstractFactory{

public:
    FactoryB();
    ~FactoryB();
    
public:
    AbstractProduct* createProduct();
};
#endif
//AbstractFactory.cpp
#include "AbstractFactory.h"

AbstractFactory::AbstractFactory(){
}
AbstractFactory::~AbstractFactory(){
}

FactoryA::FactoryA(){
}
FactoryA::~FactoryA(){
}
AbstractProduct* FactoryA::createProduct(){
    AbstractProduct* temp = NULL;
    temp = new ProductA();
    return temp;
}

FactoryB::FactoryB(){
}
FactoryB::~FactoryB(){
}
AbstractProduct* FactoryB::createProduct(){
    AbstractProduct* temp = NULL;
    temp = new ProductB();
    return temp;
}
//client.cpp
#include "AbstractFactory.h"

int main(){
    AbstractFactory* factory = new FactoryA();
    AbstractProduct* product = factory->createProduct();
    product->operation();
    delete product;
    product = NULL;
    delete factory;
    factory = NULL;
    
    factory = new FactoryB();
    product = factory->createProduct();
    product->operation();
    delete product;
    product = NULL;
    delete factory;
    factory = NULL;
    return 0;
}
三、抽象工廠模式

    抽象工廠模式就變得比工廠模式更為複雜,就像上面提到的缺點一樣,工廠模式和簡單工廠模式要求產品子類必須要是同一型別的,擁有共同的方法,這就限制了產品子類的擴充套件。於是為了更加方便的擴充套件,抽象工廠模式就將同一類的產品子類歸為一類,讓他們繼承同一個抽象子類,我們可以把他們一起視作一組,然後好幾組產品構成一族。

    此時,客戶端要使用時必須知道是哪一個工廠並且是哪一組的產品抽象類。每一個工廠子類負責產生一族產品,而子類的一種方法產生一種型別的產品。在客戶端看來只有AbstractProductA和AbstractProductB兩種產品,使用的時候也是直接使用這兩種產品。而通過工廠來識別是屬於哪一族產品。

    產品ProductA_1和ProductB_1構成一族產品,對應於有Factory1來建立,也就是說Factory1總是建立的ProductA_1和ProductB_1的產品,在客戶端看來只需要知道是哪一類工廠和產品組就可以了。一般來說, ProductA_1和ProductB_1都是適應同一種環境的,所以他們會被歸為一族。

常用的場景

    例如Linux和windows兩種作業系統下,有2個掛件A和B,他們在Linux和Windows下面的實現方式不同,Factory1負責產生能在Linux下執行的掛件A和B,Factory2負責產生能在Windows下執行的掛件A和B,這樣如果系統環境發生變化了,我們只需要修改工廠就行了。

優點

    1.封裝了產品的建立,使得不需要知道具體是哪種產品,只需要知道是哪個工廠就行了。

    2.可以支援不同型別的產品,使得模式靈活性更強。

    3.可以非常方便的使用一族中間的不同型別的產品。

缺點

    1.結構太過臃腫,如果產品型別比較多,或者產品族類比較多,就會非常難於管理。

    2.每次如果新增一組產品,那麼所有的工廠類都必須新增一個方法,這樣違背了開放-封閉原則。所以一般適用於產品組合產品族變化不大的情況。
【示例】

//AbstractProductA.h
#ifndef _ABSTRACTPRODUCTA_H_
#define _ABSTRACTPRODUCTA_H_

#include <stdio.h>

class AbstractProductA{
public:
    AbstractProductA();
    virtual ~AbstractProductA();
    
public:
    virtual void operationA() = 0;
};
class ProductA_1:public AbstractProductA{
public:
    ProductA_1();
    virtual ~ProductA_1();
    
public:
    void operationA();
};
class ProductA_2:public AbstractProductA{
public:
    ProductA_2();
    ~ProductA_2();
    
public:
    void operationA();
};

#endif
//AbstractProductA.cpp
#include "AbstractProductA.h"

AbstractProductA::AbstractProductA(){
}
AbstractProductA::~AbstractProductA(){
}

ProductA_1::ProductA_1(){
}
ProductA_1::~ProductA_1(){
}
void ProductA_1::operationA(){
    fprintf(stderr,"productA_1 operation!\n");
}

ProductA_2::ProductA_2(){
}
ProductA_2::~ProductA_2(){
}
void ProductA_2::operationA(){
    fprintf(stderr,"productA_2 operation!\n");
}
//AbstractProductB.h
#ifndef _ABSTRACTPRODUCTB_H_
#define _ABSTRACTPRODUCTB_H_

#include <stdio.h>

class AbstractProductB{
public:
    AbstractProductB();
    virtual ~AbstractProductB();
    
public:
    virtual void operationB() = 0;
};

class ProductB_1:public AbstractProductB{
public:
    ProductB_1();
    virtual ~ProductB_1();
    
public:
    void operationB();
};

class ProductB_2:public AbstractProductB{
public:
    ProductB_2();
    ~ProductB_2();
    
public:
    void operationB();
};
#endif
//AbstractProductB.cpp
#include "AbstractProductB.h"

AbstractProductB::AbstractProductB(<