筆記十三:設計模式之簡單工廠模式、工廠方法模式、抽象工廠模式
引言:
假設現在有一個超市(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++實現》