本文展示了实际项目中使用到的一个工厂模式实现,在向系统中添加新类型时,只需要在新类型的实现文件这一处做改动,将新增类型对应用程序代码的干扰降到了最低。
这个工厂实现的基本思想是:继承自同一个接口的新类型,通过一个函数来创建其对象,利用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;
}
非常简单,不必要解释了。下面是程序执行的结果:
好啦,到现在为止,一个简单好用的简单工厂模式实现介绍完毕,我特意做了简化,以便能更好的理解实现的思路,在实际的项目中,稍微复杂了一些。
回顾:
- 设计模式介绍之一:开篇概述
- 设计模式介绍之二:单例模式(Singleton)
- 设计模式介绍之三:命令模式(command)
- 设计模式介绍之四:模板方法(Template Method)模式
- 设计模式介绍之五:工厂模式(factory)