上回《大话设计模式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()函数。