我们在实现一个软件系统时,经常遇到添加新类型的情况,没有设计模式经验的开发者会采取比较直接的方式,定义一个类,在用这个类的模块中引入该类所在的头文件,使用 new 操作符从堆上分配一个对象,不需要的时候调用 delete 来删除对象回收内存。随着类型越来越多,这种分配对象的操作遍布代码各个模块,一旦类的定义发生变化,尤其是修改或增加了新的接口,就要到处检查修改代码,系统大了,甚至会遗漏一些需要修改的地方,导致不可预期的结果。
工厂模式就是用来解决上述问题的,它封装对象的创建,使得对象的创建集中在一个地方,添加新类型只需要改动这个地方,新的类型就会应用到系统中,甚至使用新类型的模块都不知道正在使用的是什么类型。
当发现需要添加新的类型到一个系统中时,最明智的做法是用多态机制为这些新类型创建一个共同的接口。用这种方法可以将系统中其余的代码与新添加的特定类型的代码分开,新类型的添加不会扰乱已存在的代码。
由于每个面向对象应用程序都需要创建对象,并且我们经常通过添加新类型来扩展应用程序,工厂模式可能是所有设计模式中最有用的模式之一。通过强制使用工厂模式,将创建对象的代码转到工厂执行,那么在增加新类型时所要做的全部工作就是只需要修改工厂。这将会大大降低因为类型增加导致的代码维护工作。
这篇文章里我们介绍简单工厂模式。下面是《C++ 编程思想》一书第二卷的工厂模式的示例(去掉了异常处理部分):
- class Shape{
- public:
- virtual void draw() = 0;
- virtual void erase() = 0;
- static Shape * factory(const string& type);
- };
- class Circle : public Shape {
- Circle(){}
- friend class Shape;
- public:
- void draw(){ cout << "Circle::draw" << endl;}
- void erase(){ cout << "Circle::erase" << endl;}
- ~Circle(){ cout << "Circle::~Circle" << endl;}
- };
- class Square : public Shape{
- Square(){}
- friend class Shape;
- public:
- void draw() { cout << "Square::draw" << endl; }
- void erase() { cout << "Square::erase" << endl; }
- ~Square(){ cout << "Square::~Square" << endl; }
- };
- Shape * Shape::factory(const string& type)
- {
- if(type == "Circle")
- {
- return new Circle();
- }
- if(type == "Square")
- {
- return new Square();
- }
- return NULL;
- }
- char* g_sl[] = { "Circle", "Square", "Square",
- "Square","Circle","Square"};
- int main()
- {
- vector<Shape*> shapes;
- for(int i = 0; i < sizeof(g_sl)/sizeof(g_sl[0]); i++)
- shapes.push_back(Shape::factory(g_sl[i]));
- for(int i = 0; i < shapes.size(); i++)
- {
- shapes[i]->draw();
- shapes[i]->erase();
- }
- return 0;
- }
函数 factory() 允许以一个参数来决定创建何种类型的 shape 。在添加新的 Shape 类型时,函数 factory() 是唯一需要修改的地方。但是这种实现——基类 Shape 必须了解每个派生类的细节——违反了面向对象设计的一个原则:基类不需要了解派生类。
这种设计比较笨拙,因为一旦新类型被添加到这种层次结构中,基类就必须更新,这对于结构框架或者类库来说都是非常不合适的。
可以通过一些策略来避免这种情况,下面我将提供一个比较精巧的实现,利用 C++ 中类的构造函数,通过注册创建函数的方法,实现一个根据关键字创建对象的工厂模式。该工厂模式实现分离工厂、产品、产品创建过程,工厂不需要知道产品如何创建,有效地隔离了新类型添加对工厂的影响——工厂不需要做任何改动,最终做到新增类型只需要在实现新类型的地方添加代码,软件中的其他地方——连工厂——都不需要改变。
下面展示了实际项目中使用到的一个工厂模式实现,在向系统中添加新类型时,只需要在新类型的实现文件这一处做改动,将新增类型对应用程序代码的干扰降到了最低。
这个工厂实现的基本思想是:继承自同一个接口的新类型,通过一个函数来创建其对象,利用C++ 中类的构造函数会被自动调用这一特性,在新类型的实现文件中定义一个静态的(辅助)类对象,在该辅助类的构造函数中,向工厂单例注册新类型的创建函数。
先看下代码,然后我们一一来解释。下面是命令接口 CommandObject 的头文件内容:
- class CommandObject
- {
- public:
- CommandObject(){}
- virtual ~CommandObject(){}
- virtual void execute() = 0;
- };
CommandObject 是一个纯虚类,作为公共的接口。
我在正式的系统中使用命令模式,封装特定的操作,传递命令对象给一些 UI 元素,如 button 等,在 UI 元素被鼠标或按键触发时,会调用关联的 CommandObject 来执行特定的命令。有关命令模式,参考文章《命令模式(command)》。
下面是命令对象工厂类的头文件:
- #ifndef COMMANDOBJECTFACTORY_H
- #define COMMANDOBJECTFACTORY_H
- #include "commandObject.h"
- typedef CommandObject * (*LPFNCREATE)();
- class CommandObjectFactory
- {
- CommandObjectFactory(const CommandObjectFactory &);
- CommandObjectFactory & operator=(const CommandObjectFactory &);
- CommandObjectFactory();
- public:
- ~CommandObjectFactory();
- static CommandObjectFactory * instance();
- CommandObject * commandObject(const char * szKeyword);
- void regist(const char * szKeyword, LPFNCREATE lpfnCreate);
- private:
- const char ** m_keywords;
- LPFNCREATE * m_functions;
- int m_iCount;
- int m_iCursor;
- };
- #define EXPORT_COMMAND_CREATOR(KEYWORD, COMMANDCLASS) \
- CommandObject * _command_object_creator_##KEYWORD() {\
- return new COMMANDCLASS;\
- }\
- class Static##KEYWORD##PluginInstance{ \
- public: \
- Static##KEYWORD##PluginInstance(){ \
- CommandObjectFactory::instance()->regist(#KEYWORD, _command_object_creator_##KEYWORD);\
- }\
- };\
- static Static##KEYWORD##PluginInstance static##KEYWORD##Instance
- #endif // COMMANDOBJECTFACTORY_H
在这个头文件中,定义了 CommandObjectFactory 这个工厂类。首先它是一个单例( singleton ),这是通常的做法,工厂类作为单例实现。关于单例,请参考文章《 单例模式(Singleton) 》。
CommandObjectFactory 定义了用于创建对象的工厂方法 commandObject ,它接受一个字符串作为关键字,内部根据这个关键字来创建命令对象。还定义了一个方法 regist ,用来向工厂内注册命令对象的创建函数,主要是被后面定义的辅助宏 EXPORT_COMMAND_CREATOR 使用,自动进行创建函数的注册。
宏 EXPORT_COMMAND_CREATOR 有两个参数,一个是与具体命令对象实现类一一对应的关键字 KEYWORD,一个是命令对象类类名 COMMANDCLASS 。这个宏非常关键,正是它帮助我们完成创建函数的注册,同时使得我们把新增类型的代码改动限制在新类型的实现文件中,对已有代码没有任何影响。
宏 EXPORT_COMMAND_CREATOR 展开后又分为几部分:辅助类声明、作用域为文件的全局静态辅助类实例、辅助类构造函数调用 CommandObjectFactory::regist() 注册创建函数。它的使用也非常简单,我们会在后面提到。
下面是 CommandObjectFactory 的实现:
- #include "commandObjectFactory.h"
- #include <iostream>
- #include <string.h>
- #include <malloc.h>
- using namespace std;
- #define INITIALISE_SIZE 32
- #define INCREMENT_SIZE 8
- static CommandObjectFactory * s_instance = 0;
- CommandObjectFactory::CommandObjectFactory()
- : m_keywords(0)
- , m_functions(0)
- , m_iCount(0)
- , m_iCursor(0)
- {
- }
- CommandObjectFactory::~CommandObjectFactory()
- {
- }
- CommandObjectFactory * CommandObjectFactory::instance()
- {
- if(!s_instance)
- {
- s_instance = new CommandObjectFactory;
- cout << "CommandObjectFactory initialised" << endl;
- }
- return s_instance;
- }
- void CommandObjectFactory::regist(const char * szKeyword, LPFNCREATE lpfnCreate)
- {
- if(!szKeyword || !lpfnCreate) return;
- //repeat check
- for(int i = 0; i < m_iCursor; ++i)
- {
- if(!strcmp(m_keywords[i], szKeyword))
- return ;
- }
- if(!m_functions)
- {
- m_functions = (LPFNCREATE*)calloc(INITIALISE_SIZE, sizeof(LPFNCREATE));
- m_keywords = (const char**)calloc(INITIALISE_SIZE, sizeof(char*));
- m_iCount = INITIALISE_SIZE;
- m_iCursor = 0;
- }
- else if( m_iCursor == m_iCount )
- {
- m_iCount += INCREMENT_SIZE;
- m_functions = (LPFNCREATE*)realloc( m_functions, m_iCount * sizeof(LPFNCREATE) );
- m_keywords = (const char**)realloc( m_keywords, m_iCount * sizeof(char*));
- }
- m_keywords[m_iCursor] = (const char *)strdup(szKeyword);
- m_functions[m_iCursor] = lpfnCreate;
- m_iCursor++;
- cout << "register create function for - " << szKeyword << endl;
- }
- CommandObject * CommandObjectFactory::commandObject(const char * szKeyword)
- {
- for(int i = 0; i < m_iCursor; ++i)
- {
- if(!strcmp(m_keywords[i], szKeyword))
- {
- return m_functions[i]();
- }
- }
- cout << "no create function for - " << szKeyword << endl;
- return 0;
- }
实现比较简单,我们在 CommandObjectFactory 内部维护了两个数组,分别存贮关键字和命令对象创建函数,两者一一对应, regist() 函数维护创建函数的注册和内部数组的动态增长。 commandObject() 函数则根据传入的关键字 szKeyword ,在内部的数组中做字符串比较,关键字匹配后定位对应的创建函数来创建命令对象。
下面看看具体命令对象类的实现和自动注册宏 EXPORT_COMMAND_CREATOR 的使用。代码:
- class ShutdownCommand : public CommandObject
- {
- public:
- void execute()
- {
- cout << endl << "ShutdownCommand::execute" << endl;
- }
- };
- EXPORT_COMMAND_CREATOR(shutdown, ShutdownCommand);
- class RebootCommand : public CommandObject
- {
- public:
- void execute()
- {
- cout << endl << "RebootCommand::execute" << endl;
- }
- };
- EXPORT_COMMAND_CREATOR(reboot, RebootCommand);
一切都很直观,不必多说了。
下面是 main() 函数,看看怎么使用命令对象工厂来创建想要的命令对象:
- int main()
- {
- CommandObject * cmd = CommandObjectFactory::instance()->commandObject("shutdown");
- cmd->execute();
- return 0;
- }
非常简单,不必要解释了。下面是程序执行的结果:
好啦,到现在为止,一个简单好用的简单工厂模式实现介绍完毕,我特意做了简化,以便能更好的理解实现的思路,在实际的项目中,稍微复杂了一些。