C++設計模式——簡單工廠模式及具體應用(含例子)
前言
Iphone 6即將面世過程中,富士康集團正在為Iphone6的面世做著最後的衝刺,生產線上的工人正忙碌著為Iphone6進行各道流程的加工操作。作為代工工廠,富士康為世界上IT巨頭,如:蘋果,惠普等加工出各種產品。市場上看到的各種品牌電腦,絕大部分來在富士康這家代工廠。在設計模式中,也存在一個類似的模式,提供一個工廠,根據需要可以創建出各種不同型別的物件,該模式稱之為簡單工程模式。
1、簡單工廠模式
簡單工廠模式並不屬於GoF 23個經典設計模式,但通常將它作為學習其他工廠模式的基礎,它的設計思想很簡單,其基本流程如下:
首先將需要建立的各種不同物件的相關程式碼封裝到不同的類中,這些類稱為具體產品類
簡單工廠模式定義如下:
簡單工廠模式(Simple Factory Pattern):定義一個工廠類,它可以根據引數的不同返回不同類的例項,被建立的例項通常都具有共同的父類。因為在簡單工廠模式中用於建立例項的方法是靜態(static)方法,因此簡單工廠模式又被稱為靜態工廠方法(Static Factory Method)模式,它屬於類建立型模式。 |
簡單工廠模式的要點在於:當你需要什麼,只需要傳入一個正確的引數,就可以獲取你所需要的物件,而無須知道其建立細節。簡單工廠模式結構比較簡單,其核心是工廠類的設計,其結構如圖1所示:
圖1 簡單工廠模式結構圖
在簡單工廠模式結構圖中包含如下幾個角色:
Factory(工廠角色):工廠角色即工廠類,它是簡單工廠模式的核心,負責實現建立所有產品例項的內部邏輯;工廠類可以被外界直接呼叫,建立所需的產品物件;在工廠類中提供了靜態的工廠方法factoryMethod(),它的返回型別為抽象產品型別Product。
Product(抽象產品角色):它是工廠類所建立的所有物件的父類,封裝了各種產品物件的公有方法,它的引入將提高系統的靈活性,使得在工廠類中只需定義一個通用的工廠方法,因為所有建立的具體產品物件都是其子類物件。
ConcreteProduct(具體產品角色):它是簡單工廠模式的建立目標,所有被建立的物件都充當這個角色的某個具體類的例項。每一個具體產品角色都繼承了抽象產品角色,需要實現在抽象產品中宣告的抽象方法。
如果不使用簡單工廠,而是直接在客戶端通過多次使用new操作和建立多個具體物件(可以是不同型別的具體物件),客戶端需要知道具體的型別,針對的是具體程式設計而不是針對介面程式設計,這違背了“針對介面程式設計而不是針對具體程式設計原則”,也違背了"迪米特法則,也就是最少朋友原則"(客戶端需要知道具體型別,而具體型別不是客戶類的直接朋友)。物件建立是一個變化的過程,因此應該封裝變化。在簡單工廠模式中,客戶端通過工廠類來建立一個產品類的例項,而無須直接使用new關鍵字來建立物件,減少了客戶端與具體物件之間的耦合,把物件的建立過程封裝在了工廠中,對客戶端而言,隱藏了物件建立的具體細節。
在使用簡單工廠模式時,首先需要對產品類進行重構,不能設計一個包羅永珍的產品類,而需根據實際情況設計一個產品層次結構,將所有產品類公共的程式碼移至抽象產品類,並在抽象產品類中宣告一些抽象方法,以供不同的具體產品類來實現。
2、OCR軟體專案的開發與實現
某OCR軟體公司根據市場需求,欲開發銀行卡識別、身份證識別、駕照識別專案。請為這些OCR識別專案開發一套統一的框架,要求能夠具有擴充套件性。(身份證識別: 意思是把身份證圖片傳送給識別引擎,由引擎對影象進行處理,把處理之後的文字資訊返回)
1.不適用模式實現方式一
專案實現程式碼如下:
#ifndef _DEVELOP_PRODUCT_H_
#define _DEVELOP_PRODUCT_H_
#include <iostream>
#include <string>
using namespace std;
//開發產品類
class DevelopProduct
{
private:
//產品名
string m_strProductName;
public:
//根據引數型別建立具體產品,在這裡為簡單起見只是儲存著專案名稱操作
void CreateProduct(string strProdectName)
{
if( 0 == strcmp(strProdectName.c_str(), "銀行卡識別專案") )
{
m_strProductName = "銀行卡識別專案";
}
else if( 0 == strcmp(strProdectName.c_str(), "身份證識別專案") )
{
m_strProductName = "身份證識別專案";
}
else if( 0 == strcmp(strProdectName.c_str(), "駕駛證識別專案") )
{
m_strProductName = "駕駛證識別專案";
}
}
//顯示建立的產品名稱
void DisplayProduct()
{
cout << "專案名稱: " << m_strProductName << endl;
}
};
#endif
DevelopProduct開發產品類,CreateProduct函式根據引數型別的不同,建立具體各個不同的識別產品。
測試程式碼實現如下:
#include <iostream>
#include "DevelopProduct.h"
using namespace std;
int main()
{
DevelopProduct * pDevelopProduct = new DevelopProduct();
pDevelopProduct->CreateProduct("銀行卡識別專案");
pDevelopProduct->DisplayProduct();
pDevelopProduct->CreateProduct("身份證識別專案");
pDevelopProduct->DisplayProduct();
pDevelopProduct->CreateProduct("駕駛證識別專案");
pDevelopProduct->DisplayProduct();
return 0;
}
編譯並執行,結果如下:
仔細發現,DevelopProduct類的CreateProduct方法帶有多個條件判斷,根據每種型別的不同,建立不同的軟體產品。整個類的程式碼相當冗長,程式碼越長,閱讀難度、維護難度和測試難度也越大。如果需要再新增一個"火車票識別專案",得修改CreateProduct程式碼,違背了開放封閉原則。因此需要對此程式碼進行重構,把建立的過程封裝起來。在重構程式碼之前,再來看一個沒有使用模式的例子。
2.不適用模式實現方式二
專案實現程式碼如下:
#ifndef _SOFT_PRODUCT_H_
#define _SOFT_PRODUCT_H_
#include <iostream>
using namespace std;
//抽象軟體產品類
class SoftProduct
{
public:
virtual void DisplayProduct() = 0;
};
//銀行卡產品
class BankCard : public SoftProduct
{
public:
void DisplayProduct()
{
cout << "專案名稱: " << "銀行卡識別專案" << endl;
}
};
//身份證產品
class IdentityCard : public SoftProduct
{
public:
void DisplayProduct()
{
cout << "專案名稱: " << "身份證識別專案" << endl;
}
};
//駕駛證產品
class DriveLicense : public SoftProduct
{
public:
void DisplayProduct()
{
cout << "專案名稱: " << "駕駛證識別專案" << endl;
}
};
#endif
SoftProduct為抽象的OCR軟體產品類,包含一個顯示方法,顯示各個專案的名稱。 BankCard銀行卡類,IdentityCard身份證類,DriveLicense駕照類是SoftProduct的子類。
測試程式碼如下:
#include <iostream>
#include "SoftProduct.h"
using namespace std;
int main()
{
SoftProduct * pSoftProduct = NULL;
//建立銀行卡識別專案
pSoftProduct = new BankCard();
pSoftProduct->DisplayProduct();
delete pSoftProduct;
pSoftProduct = NULL;
//建立身份證識別專案
pSoftProduct = new IdentityCard();
pSoftProduct->DisplayProduct();
delete pSoftProduct;
pSoftProduct = NULL;
//建立駕駛證識別專案
pSoftProduct = new DriveLicense();
pSoftProduct->DisplayProduct();
delete pSoftProduct;
pSoftProduct = NULL;
return 0;
}
編譯並執行,結果如下:
使用繼承的方式,可以根據需要進行擴充套件。如果需要新增一個"火車票識別專案",則只需要定義一個火車票識別專案類,繼承於抽象產品類SoftProduct就可以了,不需要對原有程式碼進行修改,滿足開放封閉原則。
但需要建立具體的軟體專案產品時,客戶端得知道具體的軟體產品型別。上面程式,客戶端得知道BankCard銀行卡類,IdentityCard身份證類,DriveLicense駕照類這三個具體型別。也就是說客戶端針對具體進行程式設計而不是針對抽象進行程式設計,違背了針對抽象進行程式設計原則。同時客戶端知道這些具體的型別,增加了類間的耦合性,也違背了迪米特法則,也就是最小朋友原則(那些具體的型別不是客戶端的直接朋友,客戶端只需要認識抽象產品類SoftProduct就可以了)。因此,發現問題後得重構上述程式碼,把物件的具體建立細節封裝起來,對客戶端隱藏建立的具體細節。
3、使用模式的實現方式
軟體產品類的實現程式碼如下:
#ifndef _SOFT_PRODUCT_H_
#define _SOFT_PRODUCT_H_
#include <iostream>
using namespace std;
//抽象軟體產品類
class SoftProduct
{
public:
virtual void DisplayProduct() = 0;
};
//銀行卡產品
class BankCard : public SoftProduct
{
public:
void DisplayProduct()
{
cout << "專案名稱: " << "銀行卡識別專案" << endl;
}
};
//身份證產品
class IdentityCard : public SoftProduct
{
public:
void DisplayProduct()
{
cout << "專案名稱: " << "身份證識別專案" << endl;
}
};
//駕駛證產品
class DriveLicense : public SoftProduct
{
public:
void DisplayProduct()
{
cout << "專案名稱: " << "駕駛證識別專案" << endl;
}
};
#endif
產品工廠的實現程式碼如下:
#ifndef _PRODUCT_FACTORY_H_
#define _PRODUCT_FACTORY_H_
#include <iostream>
#include <string>
#include "SoftProduct.h"
using namespace std;
//產品工廠
class ProductFactory
{
public:
//靜態方法,根據型別建立具體產品
static SoftProduct * CreateProduct(string strProductName)
{
SoftProduct * pSoftProduct = NULL;
if( 0 == strcmp(strProductName.c_str(), "銀行卡識別專案") )
{
pSoftProduct = new BankCard();
}
else if( 0 == strcmp(strProductName.c_str(), "身份證識別專案") )
{
pSoftProduct = new IdentityCard();
}
else if( 0 == strcmp(strProductName.c_str(), "駕駛證識別專案") )
{
pSoftProduct = new DriveLicense();
}
return pSoftProduct;
}
};
#endif
添加了一個產品工廠類ProductFactory,該類有一個靜態CreateProduct方法,根據引數型別的不同建立不同的具體軟體專案產品。CreateProduct內部封裝了具體物件的建立細節,對客戶端而言,不再需要具體的物件型別了,客戶端完全針對介面進行程式設計,對客戶端隱藏了物件建立的細節。
測試程式實現程式碼如下:
#include <iostream>
#include "SoftProduct.h"
#include "ProductFactory.h"
using namespace std;
int main()
{
SoftProduct * pSoftProduct = NULL;
//建立銀行卡識別專案
pSoftProduct = ProductFactory::CreateProduct("銀行卡識別專案");
pSoftProduct->DisplayProduct();
delete pSoftProduct;
pSoftProduct = NULL;
//建立身份證識別專案
pSoftProduct = ProductFactory::CreateProduct("身份證識別專案");
pSoftProduct->DisplayProduct();
delete pSoftProduct;
pSoftProduct = NULL;
//建立駕駛證識別專案
pSoftProduct = ProductFactory::CreateProduct("駕駛證識別專案");
pSoftProduct->DisplayProduct();
delete pSoftProduct;
pSoftProduct = NULL;
return 0;
}
編譯並執行,結果如下:
3、簡單工廠模式總結
簡單工廠模式提供了專門的工廠類用於建立物件,將物件的建立和物件的使用分離開,它作為一種最簡單的工廠模式在軟體開發中得到了較為廣泛的應用。
1.主要優點
簡單工廠模式的主要優點如下:
(1) 工廠類包含必要的判斷邏輯,可以決定在什麼時候建立哪一個產品類的例項,客戶端可以免除直接建立產品物件的職責,而僅僅“消費”產品,簡單工廠模式實現了物件建立和使用的分離。也就是說工廠類封裝了變化,封裝了物件建立的具體細節,對客戶端隱藏物件建立的細節,使得客戶類針對介面進行程式設計。滿足"針對介面程式設計而不是針對具體程式設計原則"。
(2) 客戶端無須知道所建立的具體產品類的類名,只需要知道具體產品類所對應的引數即可,對於一些複雜的類名,通過簡單工廠模式可以在一定程度減少使用者的記憶量。滿足"迪米特法則,也就是和直接朋友通訊原則"。
(3) 具體物件建立是一個變化過程,因此把物件的建立封裝起來,體現了"封裝變化原則"。
2.主要缺點
簡單工廠模式的主要缺點如下:
(1) 由於工廠類集中了所有產品的建立邏輯,職責過重,一旦不能正常工作,整個系統都要受到影響,違背"單一原則"。
(2)使用簡單工廠模式勢必會增加系統中類的個數(引入了新的工廠類),增加了系統的複雜度和理解難度。
(3)系統擴充套件困難,一旦新增新產品就不得不修改工廠邏輯,在產品型別較多時,有可能造成工廠邏輯過於複雜,不利於系統的擴充套件和維護。違背"開發封閉原則"。
(4)簡單工廠模式由於使用了靜態工廠方法,造成工廠角色無法形成基於繼承的等級結構。
3.簡單工程模式具體應用
(1)通常在使用word辦公軟體的時候,會根據需要繪製出餅狀圖,柱狀圖,折線圖等圖形。可以提供一個工廠類,根據使用者的選擇創建出不同型別的圖形。
(2)QQ空間背景樣式,部落格背景樣式等都提供了各種風格的樣式。提供一個工廠,根據使用者選擇的具體風格樣式,創建出各個不同的背景風格,用來裝飾QQ空間。
(3)網頁下載工具的開發: 根據需要可以下載新浪網頁、騰訊網頁、搜狐網頁等。根據使用者的選擇,把網頁型別傳進工廠,將下載該型別的網頁內容。
(4)淘寶購物最後一個支付環節,可以選擇貨到付款、網上銀行、支付寶等型別支付。使用者可以選擇具體的支付方式完成訂單,這也是簡單工廠模式的一種應用。
(5)電影院打折演算法: VIP5折、學生票5折、成人票正常收費等打折演算法。
(6)多功能計算器的開發:封裝加減乘除等運算操作(大話設計模式的例子)
(7)在很多遊戲場合,遊戲角色可以選擇各種各樣的武器,如:手槍、AK47、步槍、大刀等。
(8)如果電腦上裝有QQ輸入法、搜狗輸入法、微軟拼音輸入法,使用者可以設定使用哪種型別的輸入法。類似的還可以設定IE瀏覽器、谷歌瀏覽器、火狐瀏覽器。可以設定word2003或者金山的WPS。這些都可以理解為簡單工廠模式的一種運用。
(9)軟體公司決策是否開發哪一種產品,銀行卡識別、身份證識別還是駕駛證識別。
(10)生活中也有很多類似的工廠: 富士康代工工廠;安踏加工廠;咖啡生產基地;沃爾瑪等超市提供各種產品供使用者使用;肯德基馬當勞等。