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
測試代碼實現如下:
- #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
測試代碼如下:
- #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
測試程序實現代碼如下:
- #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)生活中也有很多類似的工廠: 富士康代工工廠;安踏加工廠;咖啡生產基地;沃爾瑪等超市提供各種產品供用戶使用;肯德基馬當勞等。