上回《大話設計模式C++版——簡單工廠模式》中指出了簡單工廠模式的缺陷,即違背了開發—封閉原則,其主要原因是由於switch的判斷結構的使用,使修改或添加新的對象時需要改動簡單工廠類的代碼,如何改造switch結構,表驅動法就可以粉墨登場了。
表驅動法的介紹見《數據驅動編程之表驅動法》。
1、面向接口編程,先改造抽象接口類IOperation
class IOperation
{
public:
IOperation() : m_nNuml(0), m_nNumr(0) {}
virtual ~IOperation() {}
virtual void SetNum(int nNuml = 0, int nNumr = 0)
{
m_nNuml = nNuml;
m_nNumr = nNumr;
}
virtual int CalculateResult() = 0;
public:
typedef IOperation* (CALLBACK* fn_CreateObject)();
protected:
int m_nNuml, m_nNumr;
};
接口基本無改動,由於表驅動法中需要在表中填入驅動函數接口,故先定義一個該接口函數的定義,用於產生具體接口對象。
2、改造接口對象類
class COperation_Add : public IOperation
{
public:
int CalculateResult()
{
return m_nNuml + m_nNumr;
}
static IOperation* CALLBACK CreateObject()
{
return new COperation_Add();
}
};
class COperation_Dec : public IOperation
{
public:
int CalculateResult()
{
return m_nNuml - m_nNumr;
}
static IOperation* CALLBACK CreateObject()
{
return new COperation_Dec();
}
};
增加了CreateObject()靜態函數,改函數的作用是用來產生本對象。由於表驅動函數是用於回調的,所以需要定義爲靜態回調函數。
3、改造工廠類,增加驅動表改造switch結構(重點)
class CClassFactory
{
public:
CClassFactory() {}
virtual ~CClassFactory() {}
BOOL AddOperationFunc(char cOperation, IOperation::fn_CreateObject func)
{
if (func)
{
m_mapOperationFunc[cOperation] = func;
return TRUE;
}
return FALSE;
}
IOperation* CreateObject(char cOperation)
{
IOperation::fn_CreateObject func = m_mapOperationFunc[cOperation];
if (func)
{
return func();
}
return NULL;
}
protected:
std::map<char, IOperation::fn_CreateObject> m_mapOperationFunc; //用map實現驅動表
};
工廠類中的switch消失了,換成了在map中以操作符爲key找對應驅動函數,然後進行調用。同時增加了添加新操作符和對應驅動函數的接口AddOperationFunc(),這樣如果需要新增加對象或者修改操作符對應的驅動函數,調用此接口即可,靈活性大大增加。
4、使用示例
void Test()
{
CClassFactory oCClassFactory;
IOperation* poIOperation = NULL;
//添加對象工廠的生產對象
oCClassFactory.AddOperationFunc('+', COperation_Add::CreateObject);
oCClassFactory.AddOperationFunc('-', COperation_Dec::CreateObject);
poIOperation = oCClassFactory.CreateObject('+');
if (poIOperation)
{
poIOperation->SetNum(2, 3);
printf("2 + 3 = %d\n", poIOperation->CalculateResult());
delete poIOperation;
}
poIOperation = oCClassFactory.CreateObject('-');
if (poIOperation)
{
poIOperation->SetNum(2, 3);
printf("2 + 3 = %d\n", poIOperation->CalculateResult());
delete poIOperation;
}
}
使用前需添加操作符和對應的驅動函數,這樣一來將用戶的任務加重了,而且需要用戶認識的類增加了,封裝性降低了。是否可進一步改進?
5、進一步改進簡單工廠類,提高封裝性
class CNewClassFactory : CClassFactory
{
public:
virtual ~CNewClassFactory() {}
virtual void Init()
{
AddOperationFunc('+', COperation_Add::CreateObject);
AddOperationFunc('-', COperation_Dec::CreateObject);
}
};
新簡單工廠類中,增加了一個初始化函數Init(),代替用戶的進行操作符和對應的驅動函數,這樣用戶只需要調用Init()函數即可,封裝性進一步提高,那麼有同學可能會問,如果要增加新的計算對象,還不得改動Init()函數代碼,這不跑了一圈又回到沒改造前的原點了。
這就得用到開放—封閉原則了,新簡單工廠類CNewClassFactory中,我們將Init()函數申明爲虛函數了,如果我們要增加新的計算對象,可以有2個途徑:
5.1 用戶在自己代碼中調用AddOperationFunc()函數進行添加。
5.2 繼承新簡單工廠類,重寫Init()函數。