概述
我們在談設計模式的時候,需要結合具體的場景來談。沒有萬能的設計模式可以適應每一個業務場景,我們只有結合實際業務場景,抓住場景中的「變」和「不變」的主體才能更好地運用設計模式來設計出優良的代碼結構。
工廠設計模式屬於讓我們靈活創建對象的模式。用於應對這樣的場景:通過一個類,在不改變它的源碼的情況下可以通過它暴露的接口構造出我們想要的對象。
通過模擬一個小場景並逐步重構來理解工廠設計模式。
需求
我們模擬一下小米公司的產品發佈。
需求:有一個主體(小米公司),可以通過它對外的接口不斷髮布我們想要的產品。
簡單實現:
#ifndef _MI_PRIMARY_H_
#define _MI_PRIMARY_H_
#include <iostream>
using std::cout;
using std::endl;
enum MiProductTypes
{
Phone = 1,
NoteBook
};
class MiPhone{/*具體產品*/};
class MiNoteBook{/*具體產品*/};
class MiPrimary
{
public:
MiPrimary(){}
~MiPrimary(){}
void* productAnnounce(MiProductTypes type)
{
void * pRet = nullptr;
switch (type)
{
case Phone:
pRet = new MiPhone;
cout << "MiPhone Announced!" << endl;
break;
case NoteBook:
pRet = new MiNoteBook;
cout << "MiNoteBook Announced!" << endl;
break;
default:
break;
}
return pRet;
}
};
#endif
注意: 這裏爲了簡便把各個產品與MiPrimary類寫在一個文件了,實際上是分開在不同文件中的。這裏的核心邏輯是定義枚舉,根據傳入的類型到對應分支創造對象。
我們如何使用這個類:
#include <iostream>
#include "MiPrimary.h"
using std::cout;
using std::endl;
int main()
{
MiPrimary mi;
mi.productAnnounce(Phone);
return 0;
}
我們先實例化一個MiPrimary對象,然後傳入事先定義好的枚舉類型,就可以得到對應的類。
缺陷: 缺陷是結合場景來講的,這裏的場景是小米公司後面還要擴展業務,目前只有手機、筆記本電腦這兩種產品。後面可能還有其他的3C產品需要發佈,那時候我們又需要在這裏新增MiProductTypes的成員,以及在switch下面新增case 分支。
優勢: 當然,優勢就是代碼簡單。邏輯清晰易懂。
思考: 如果說我們的MiPrimary類寫好了就不想再變動了,以後新增產品的時候也不用改MiPrimary代碼就可以直接發佈,那我們該怎麼設計?
工廠設計模式
針對這個場景,前面設計的缺陷在於MiPrimary編譯時依賴於具體的某個產品的實現。工廠設計模式通過抽象一個工廠基類,讓MiPrimary依賴這個工廠基類,把new操作留給工廠基類,來避免對具體產品的依賴。
工廠設計模式要點:
- 工廠基類
- 產品基類
具體產品繼承產品基類,還需要一個與具體工廠,繼承自工廠基類,通過具體工廠new 具體產品。
UML關係如下用MiCompany類代替MiPrimary類,加以區分:
我們在設計系統的時候主要抓住系統的變與不變:在上面這個UML圖中,變的部分我們用藍色線圖框出,不變的,穩定的部分我們用紅色線圖框出。
下面是具體的代碼實現:
不變的主體類MiCompany:
//file name:MiCompany.h
#ifndef _MICOMPANY_H_
#define _MICOMPANY_H_
#include <assert.h>
#include "IFactory.h"
class MiCompany
{
public:
MiCompany()
{
m_pFactory = nullptr;
}
~MiCompany(){}
void setFactory(IFactory* pFactory)
{
m_pFactory = pFactory;
}
void productAnnounce()//若指針傳出,內存交由外部處理
{
if(m_pFactory)
{
m_pFactory->getProduct()->doSomeWork();
}
}
private:
IFactory* m_pFactory;
};
#endif
工廠基類IFactory:
//file name:IFactory.h
#ifndef _IFACTORY_H_
#define _IFACTORY_H_
#include "IProduct.h"
class IFactory
{
public:
virtual IProduct* getProduct() = 0;
};
#endif
產品基類IProduct:
//file name:IProduct.h
#ifndef _PRODUCT_H_
#define _PRODUCT_H_
#include <iostream>
using std::cout;
using std::endl;
class IProduct
{
public:
IProduct(){}
virtual ~IProduct(){}
virtual void doSomeWork() = 0;
};
#endif
具體產品工廠MiPhoneFactory:
//file name:MiPhoneFactory.h
#ifndef _MIPHONE_FACTORY_H_
#define _MIPHONE_FACTORY_H_
#include "IFactory.h"
#include "MiPhone.h"
class MiPhoneFactory : public IFactory
{
public:
MiPhoneFactory()
{
m_pMiPhone = nullptr;
}
~MiPhoneFactory()
{
delete m_pMiPhone;
m_pMiPhone = nullptr;
}
IProduct* getProduct()//根據需求也可做成單例
{
if(m_pMiPhone == nullptr)
{
m_pMiPhone = new MiPhone;
}
return m_pMiPhone;
}
private:
MiPhone* m_pMiPhone;
};
#endif
具體產品MiNoteBook類:
//file name:MiNoteBook.h
#ifndef _MI_NOTEBOOK_H_
#define _MI_NOTEBOOK_H_
#include "IProduct.h"
class MiNoteBook : public IProduct
{
public:
MiNoteBook()
{
cout << "MiNoteBook Created!" << endl;
}
~MiNoteBook()
{
}
virtual void doSomeWork()
{
cout << "MiNoteBook doSomeWork! " << endl;
}
};
#endif
具體產品工廠類MiNoteBookFactory.h:
//filename:MiNoteBookFactory.h
#ifndef _MI_NOTEBOOK_FACTORY_H_
#define _MI_NOTEBOOK_FACTORY_H_
#include "IFactory.h"
#include "MiNoteBook.h"
class MiNoteBookFactory : public IFactory
{
public:
MiNoteBookFactory()
{
m_pMiNoteBook = nullptr;
}
virtual IProduct* getProduct()//這裏根據需要可以做成一個單例
{
if(m_pMiNoteBook)
{
m_pMiNoteBook = new MiNoteBook;
}
return m_pMiNoteBook;
}
private:
MiNoteBook* m_pMiNoteBook;
};
#endif
使用案例:
//file name: main.cc
#include <iostream>
using std::cout;
using std::endl;
#include "MiCompany.h"
#include "MiPhoneFactory.h"
#include "MiNoteBookFactory.h"
int main()
{
MiCompany mi;
MiPhoneFactory phoneFactory;
mi.setFactory(&phoneFactory);
mi.productAnnounce();
MiNoteBookFactory notebookFactory;
mi.setFactory(¬ebookFactory);
mi.productAnnounce();
return 0;
}
總結:
我們在MiCompany確定的情況下,要增加新的產品發佈的話,我們除了要新增產品以外,還要新增一個針對這個產品的工廠搭配使用,這樣才符合目前這個工廠模式的框架。那還有沒有更酷的解決方案呢?
通常來說,代碼的騷操作越多,越難以理解。我們後續會繼續改進這個設計。