1. 程式人生 > >筆記十三:設計模式之簡單工廠模式、工廠方法模式、抽象工廠模式

筆記十三:設計模式之簡單工廠模式、工廠方法模式、抽象工廠模式

引言:
假設現在有一個超市(Market),超市銷售餅乾(Biscuit)、水果(Fruit)、飲料(Drink)三種食品。
這裡寫圖片描述
按照常規,我們建立三個類:class Biscuit、class Fruit、class Drink。

class Biscuit{
public:
    Biscuit(){}
    ~Biscuit(){}
    void Show(){ cout << "Hi,customers! I'm Biscuit!" << endl; }
};

class Fruit{
public:
    Fruit(){}
    ~Fruit(){}
    void
Show(){ cout << "Hi,customers! I'm Fruit!" << endl; } }; class Drink{ public: Drink(){} ~Drink(){} void Show(){ cout << "Hi,customers! I'm Drink!" << endl; } };

現在假設我們有餅乾2件、水果2件、飲料2件。則:

Biscuit* pBiscuit1 = new Biscuit();
pBiscuit1->Show();
Biscuit* pBiscuit2 =
new Biscuit(); pBiscuit2->Show(); Fruit* pFruit1 = new Fruit(); pFruit1->Show(); Fruit* pFruit2 = new Fruit(); pFruit2->Show(); Drink* pDrink1 = new Drink(); pDrink1->Show(); Drink* pDrink2 = new Drink(); pDrink2->Show();

這裡寫圖片描述

1、從上述程式碼中,我們發現:1)每一個具體的食品的建立都暴露在使用者面前2)假設每一類食品的數目增多,若100個,那麼程式碼中得new 100*3次,程式碼冗餘量變大。

2、當用戶不需要了解到具體食品的建立細節,而只需要知道它是什麼,同時又能減少程式碼冗餘,有沒有什麼好的辦法呢?

簡單工廠模式

文章前面講餅乾、水果、飲料進行單獨構造類,三者之間沒有任何關聯。為了對它們進行封裝,可以根據它們之間的某種聯絡構造成一個整體,這樣細節之處,就被封裝在這個整體之中,而使用者無法得知(也不需要知道)。由於三種商品屬性均為食品,因此, 基類與派生類的概念被引入,這樣,三種食品就有了關聯。同時,“超市”這一概念有什麼用呢?我們可以把食品類構造的細節操作放在超市類中,這樣就完成了 封裝

這裡寫圖片描述

這裡將簡單工廠模式的結構與超市-餅乾-水果-飲料物件進行對映。具體程式碼如下:

/************************************
 * Function:    簡單工廠模式案例
 * @Copyright:
 * Author:      by sun
 * Date:        2016-07-13
************************************/

//商品名
typedef enum ProductTag
{
    BiscuitTag,
    FruitTag,
    DrinkTag
} PRODUCTTAG;

//食品基類
class Product{
public:
    Product(){}
    ~Product(){}
    virtual void Show(){ cout << "Hi, customers! I'm Product!" << endl; }
};

//餅乾
class Biscuit :public Product
{
public:
    Biscuit(){}
    ~Biscuit(){}
    void Show(){ cout << "Hi,customers! I'm Biscuit!" << endl; }
};

//水果
class Fruit :public Product
{
public:
    Fruit(){}
    ~Fruit(){}
    void Show(){ cout << "Hi,customers! I'm Fruit!" << endl; }
};

//飲料
class Drink :public Product
{
public:
    Drink(){}
    ~Drink(){}
    void Show(){ cout << "Hi,customers! I'm Drink!" << endl; }
};

//超市
class Market{
public:
    Market(){}
    ~Market(){}
    Product* SellProduct(PRODUCTTAG ItemTag) const;
};

Product* Market::SellProduct(PRODUCTTAG ItemTag) const
{
    switch (ItemTag)
    {
    case BiscuitTag:
        return new Biscuit();
        break;
    case FruitTag:
        return new Fruit();
    case DrinkTag:
        return new Drink();
        break;
    }
};
Market* pMarket = new Market();
Product* pBiscuit = pMarket->SellProduct(BiscuitTag);
pBiscuit->Show();
Product* pFruit = pMarket->SellProduct(FruitTag);
pFruit->Show();
Product* pDrink = pMarket->SellProduct(DrinkTag);
pDrink->Show();

Product* pBiscuit1 = pMarket->SellProduct(BiscuitTag);
pBiscuit1->Show();
Product* pFruit1 = pMarket->SellProduct(FruitTag);
pFruit1->Show();
Product* pDrink1 = pMarket->SellProduct(DrinkTag);
pDrink1->Show();

1、可以看出,關於餅乾、飲料、水果的具體的構造操作(new)不再直接暴露在使用者面前,而是封裝在Market類中,第一個目的已經完成。

2、同樣是每件商品均產生2個,程式碼中出現的new的個數仍舊只有3個,當每種食品為100個的時候,儘管實際呼叫了100*3次new,但寫在程式碼中的new也還是隻有3個,減少了程式碼冗餘,完成了第2個目標。

現在,當食品種類增加,比如出現了堅果(nut)、麵包(bread),那麼如何修改上述程式碼呢?
第一:列舉型別擴充

//商品名
typedef enum ProductTag
{
    BiscuitTag,
    FruitTag,
    DrinkTag,
    NutTag,
    BreadTag
} PRODUCTTAG;

第二:以Product作為基類的派生類增加

//堅果
class Nut :public Product
{
public:
    Nut(){}
    ~Nut(){}
    void Show(){ cout << "Hi,customers! I'm Nut!" << endl; }
};

//麵包
class Bread :public Product
{
public:
    Bread(){}
    ~Bread(){}
    void Show(){ cout << "Hi,customers! I'm Bread!" << endl; }
};

第三:Market類修改

switch (ItemTag)
    {
    case BiscuitTag:
        return new Biscuit();
        break;
    case FruitTag:
        return new Fruit();
    case DrinkTag:
        return new Drink();
        break;
    case NutTag:
        return new Nut();
        break;
    case BreadTag:
        return new Bread();
        break;
    }
Market* pMarket = new Market();
    Product* pBiscuit = pMarket->SellProduct(BiscuitTag);
    pBiscuit->Show();
    Product* pFruit = pMarket->SellProduct(FruitTag);
    pFruit->Show();
    Product* pDrink = pMarket->SellProduct(DrinkTag);
    pDrink->Show();

    Product* pNut = pMarket->SellProduct(NutTag);
    pNut->Show();
    Product* pBread = pMarket->SellProduct(BreadTag);
    pBread->Show();

這裡寫圖片描述

這裡我們發現,當增加食品種類時,Market類中switch函式需要修改,若增加的Nut和Bread操作不當,則會影響已經建立的Biscuit、Fruit、Drink。且,測試的時候,由於switch程式碼被修改,則其整個功能需全部重新測試,重複已完成工作量。——這就是通常意義上說的違反了開閉原則。

簡單工廠模式:

優點:封裝了物件的介面,減少程式碼冗餘
缺點:耦合度較高,擴充套件性不好

工廠方法模式

在簡單工廠模式的基礎上,為了減少耦合度,提高程式碼的擴充套件性,對“工廠類”添加了一個抽象類,將工廠共同的動作抽象出來。而具體的行為由子類本身去實現,讓子類決定生產什麼樣的產品。

這裡寫圖片描述

FactoryA、FactoryB、FactoryC是新增的Factory的子類,每個子類分管自己的轄區。

/****************************************
 * Function:    工廠方法模式模式案例
 * @Copyright: 
 * Author:      by sun
 * Date:        2016-07-13
 ***************************************/


//食品基類
class Product{
public:
    Product(){}
    ~Product(){}
    virtual void Show(){ cout << "Hi, customers! I'm Product!" << endl; }
};


//餅乾
class Biscuit :public Product
{
public:
    Biscuit(){}
    ~Biscuit(){}
    void Show(){ cout << "Hi,customers! I'm Biscuit!" << endl; }
};

//水果
class Fruit :public Product
{
public:
    Fruit(){}
    ~Fruit(){}
    void Show(){ cout << "Hi,customers! I'm Fruit!" << endl; }
};

//飲料
class Drink :public Product
{
public:
    Drink(){}
    ~Drink(){}
    void Show(){ cout << "Hi,customers! I'm Drink!" << endl; }
};

//超市
class Market{
public:
    Market(){};
    ~Market(){};
    virtual Product* SellProduct() { return new Product(); }

};

//餅乾區
class BiscuitArea:public Market
{
public:
    BiscuitArea(){};
    ~BiscuitArea(){};
    Product* SellProduct(){ return new Biscuit(); }
};

//水果區
class FruitArea :public Market
{
public:
    FruitArea(){};
    ~FruitArea(){};
    Product* SellProduct(){ return new Fruit(); }

};

//飲料區
class DrinkArea :public Market
{
public:
    DrinkArea(){};
    ~DrinkArea(){};
    Product* SellProduct(){ return new Drink(); }
};
Market* pBiscuitArea = new BiscuitArea();
Product* pBiscuit = pBiscuitArea->SellProduct();
    pBiscuit->Show();

Market* pFruitArea = new FruitArea();
Product* pFruit = pFruitArea->SellProduct();
pFruit->Show();

Market* pDrinkArea = new DrinkArea();
Product* pDrink = pDrinkArea->SellProduct();
pDrink->Show();

這裡寫圖片描述

1、分析程式碼可知,當要建立某一種食品時,只需要例項化它對應的管轄區類(例如,餅乾-》餅乾區超市子類BiscuitArea),結構清晰

2、同樣的,假設現在每種食品有2件,那麼使用者端操作的程式碼為:

Market* pBiscuitArea = new BiscuitArea();
    Product* pBiscuit = pBiscuitArea->SellProduct();
    pBiscuit->Show();

    Market* pFruitArea = new FruitArea();
    Product* pFruit = pFruitArea->SellProduct();
    pFruit->Show();

    Market* pDrinkArea = new DrinkArea();
    Product* pDrink = pDrinkArea->SellProduct();
    pDrink->Show();

    Market* pBiscuitArea1 = new BiscuitArea();
    Product* pBiscuit1 = pBiscuitArea1->SellProduct();
    pBiscuit1->Show();

    Market* pFruitArea1 = new FruitArea();
    Product* pFruit1 = pFruitArea1->SellProduct();
    pFruit1->Show();

    Market* pDrinkArea1 = new DrinkArea();
    Product* pDrink1 = pDrinkArea1->SellProduct();
    pDrink->Show();

這裡寫圖片描述

我們發現使用者端使用new的次數,並沒有減少。似乎減少程式碼冗餘這個功能沒有實現???至少在使用者端沒有減少,我是這麼認為的。

3、同樣,現在新增了Nut和Bread這2種食品。如何擴充套件程式碼呢?
第一:擴充套件食品派生類

//堅果
class Nut :public Product
{
public:
    Nut(){}
    ~Nut(){}
    void Show(){ cout << "Hi,customers! I'm Nut!" << endl; }
};

//麵包
class Bread :public Product
{
public:
    Bread(){}
    ~Bread(){}
    void Show(){ cout << "Hi,customers! I'm Bread!" << endl; }
};

第二:擴充套件超市子類

//堅果區
class NutArea :public Market
{
public:
    NutArea(){};
    ~NutArea(){};
    Product* SellProduct(){ return new Nut(); }
};

//麵包區
class BreadArea :public Market
{
public:
    BreadArea(){};
    ~BreadArea(){};
    Product* SellProduct(){ return new Bread(); }
};

第三:增加使用者端程式碼

Market* pBiscuitArea = new BiscuitArea();
Product* pBiscuit = pBiscuitArea->SellProduct();
pBiscuit->Show();

Market* pFruitArea = new FruitArea();
Product* pFruit = pFruitArea->SellProduct();
pFruit->Show();

Market* pDrinkArea = new DrinkArea();
Product* pDrink = pDrinkArea->SellProduct();
pDrink->Show();

Market* pNutArea = new NutArea();
Product* pNut = pNutArea->SellProduct();
pNut->Show();

Market* pBreadArea = new BreadArea();
Product* pBread = pBreadArea->SellProduct();
pBread->Show();

這裡寫圖片描述

分析上述程式碼,可以知道,當增加Nut和Bread這2個新品種時,擴充套件的程式碼與原始程式碼基本上沒有交集,無非是多增加一個新品種,食品擴充套件類增加一個派生類,超市子類增加一個,原始程式碼不會進行修改,因此,功能也無需重新測試。

工廠方法模式:

優點:使得具體化類的工作延遲到了子類中,耦合度低,擴充套件性好。工廠端符合開閉原則。
缺點:使用者端程式碼並沒有實現減少程式碼冗餘的工作。

抽象工廠模式

當前餅乾、水果、飲料都統稱為食品。超市決定為了使顧客吃的更健康,決定對食品進行分類:成年人食品+兒童食品。則分類如下:

這裡寫圖片描述

在工廠模式的基礎上,對Product類進行修改,則可變為AdultProduct和ChildProduct類。相應地,餅乾專區(BiscuitArea)的餅乾也分別來自這2類中的餅乾(ChildBiscuit和AdultBiscuit)。因為篇幅原因,在抽象工廠模式的UML圖中,省略了水果的分類。

這裡寫圖片描述

/***************************************
 * Function:    抽象工廠模式
 * @Copyright:
 * Author:      by sun
 * Date:        2016-07-13
 **************************************/


class ChildProduct{
public:
    ChildProduct(){}
    ~ChildProduct(){}
    virtual void Show(){ cout << "Hi,customers! I'm ChildProduct" << endl; }
};

class ChildBiscuit:public ChildProduct
{
public:
    ChildBiscuit(){}
    ~ChildBiscuit(){}
    void Show(){ cout << "Hi,customers! I'm ChildBiscuit" << endl; }
};

class ChildDrink :public ChildProduct
{
public:
    ChildDrink(){}
    ~ChildDrink(){}
    void Show(){ cout << "Hi,customers! I'm ChildDrink" << endl; }
};

class AdultProduct
{
public:
    AdultProduct(){}
    ~AdultProduct(){}
    virtual void Show(){ cout << "Hi, customers! I'm AdultProduct" << endl; }
};

class AdultBiscuit:public AdultProduct
{
public:
    AdultBiscuit(){}
    ~AdultBiscuit(){}
    void Show(){ cout << "Hi,customers! I'm AdultBiscuit" << endl; }

};

class AdultDrink :public AdultProduct
{
public:
    AdultDrink(){}
    ~AdultDrink(){}
    void Show(){ cout << "Hi,customers! I'm AdultDrink" << endl; }

};

class Market
{
public:
    Market(){}
    ~Market(){}
    virtual AdultProduct* SellAdultProduct(){ return new AdultProduct(); }
    virtual ChildProduct* SellChildProduct(){ return new ChildProduct(); }

};

class BiscuitArea :public Market
{
public:
    BiscuitArea(){}
    ~BiscuitArea(){}
    AdultProduct* SellAdultProduct(){ return new AdultBiscuit(); }
    ChildProduct* SellChildProduct(){ return new ChildBiscuit(); }
};

class DrinkArea :public Market
{
public:
    DrinkArea(){}
    ~DrinkArea(){}
    AdultProduct* SellAdultProduct(){ return new AdultDrink(); }
    ChildProduct* SellChildProduct(){ return new ChildDrink(); }
};
Market* pBiscuitArea = new BiscuitArea();
ChildProduct* pChildBiscuit = pBiscuitArea->SellChildProduct();
pChildBiscuit->Show();
AdultProduct* pAdultBiscuit = pBiscuitArea->SellAdultProduct();
pAdultBiscuit->Show();

Market* pDrinkArea = new DrinkArea();
ChildProduct* pChildDrink = pDrinkArea->SellChildProduct();
pChildDrink->Show();
AdultProduct* pAdultDrink = pDrinkArea->SellAdultProduct();
pAdultDrink->Show();

這裡寫圖片描述

這裡打破了產品單一的限制,可以對Product進行更為細緻的劃分,由一個變成了一組。

同樣地,當增加Bread和Nut時,我們對之細分為成年人和兒童版。

第一:擴充套件兒童Nut和Bread

class ChildNut :public ChildProduct
{
public:
    ChildNut(){}
    ~ChildNut(){}
    void Show(){ cout << "Hi,customers! I'm ChildNut" << endl; }
};

class ChildBread :public ChildProduct
{
public:
    ChildBread(){}
    ~ChildBread(){}
    void Show(){ cout << "Hi,customers! I'm ChildBread" << endl; }
};

第二:擴充套件成年人Bread和Nut

class AdultNut :public AdultProduct
{
public:
    AdultNut(){}
    ~AdultNut(){}
    void Show(){ cout << "Hi,customers! I'm AdultNut" << endl; }

};

class AdultBread :public AdultProduct
{
public:
    AdultBread(){}
    ~AdultBread(){}
    void Show(){ cout << "Hi,customers! I'm AdultBread" << endl; }

};

第三:擴充套件Nut和Bread的超市子類

class NutArea :public Market
{
public:
    NutArea(){}
    ~NutArea(){}
    AdultProduct* SellAdultProduct(){ return new AdultNut(); }
    ChildProduct* SellChildProduct(){ return new ChildNut(); }
};

class BreadArea :public Market
{
public:
    BreadArea(){}
    ~BreadArea(){}
    AdultProduct* SellAdultProduct(){ return new AdultBread(); }
    ChildProduct* SellChildProduct(){ return new ChildBread(); }
};

第四:增加使用者端程式碼

Market* pBiscuitArea = new BiscuitArea();
    ChildProduct* pChildBiscuit = pBiscuitArea->SellChildProduct();
    pChildBiscuit->Show();
    AdultProduct* pAdultBiscuit = pBiscuitArea->SellAdultProduct();
    pAdultBiscuit->Show();

    Market* pDrinkArea = new DrinkArea();
    ChildProduct* pChildDrink = pDrinkArea->SellChildProduct();
    pChildDrink->Show();
    AdultProduct* pAdultDrink = pDrinkArea->SellAdultProduct();
    pAdultDrink->Show();

    Market* pNutArea = new NutArea();
    ChildProduct* pChildNut = pNutArea->SellChildProduct();
    pChildNut->Show();
    AdultProduct* pAdultNut = pNutArea->SellAdultProduct();
    pAdultNut->Show();

    Market* pBreadArea = new BreadArea();
    ChildProduct* pChildBread = pBreadArea->SellChildProduct();
    pChildBread->Show();
    AdultProduct* pAdultBread = pBreadArea->SellAdultProduct();
    pAdultBread->Show();

這裡寫圖片描述

抽象工廠模式:

優點:將“單一抽象產品類”擴充套件到“產品族“,產品型別增多
缺點:結構比較臃腫,當產品增多,系統比較龐大,難以管理


參考文獻:
1、《GoF+23種設計解析附C++實現》