本文展示了实际项目中使用到的一个工厂模式实现,在向系统中添加新类型时,只需要在新类型的实现文件这一处做改动,将新增类型对应用程序代码的干扰降到了最低。
这个工厂实现的基本思想是:继承自同一个接口的新类型,通过一个函数来创建其对象,利用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)