来个绘图经典的例子吧。基类是Shape,子类是Circle,Triangle等等。肯定有人写过这样的代码:
enum SHAPE_DEFINE
someFunction()
{
SHAPE_DEFINE shape_variable;
……
Shape * shap;
switch(shape_variable)
{
case CIRCLE:
shape = new Circle;
break;
case TRIANGLE:
shape = new Trigangle;
break;
.....
}
//do something with shape
.....
}
我们因该尽量避免在工厂模式的Factory类出现类似代码。原因是:使用case语句不利于以后的扩充和维护。当添加新的产品的时候,我们最好不要修改Factory类中的代码,包括添加新产品的头文件和修改创建新产品的case语句。我们希望的代码如下
someFunction()
{
Shape * shap = ShapeFactory::getInstance(“Circle”);
//do something with shape
}
要达到以上目标,我们将讨论在C++中两个重要实现:
1) 由名字创建对象
2) 新产品在工厂模式中的自动登记功能(auto-register)
由名字创建对象其实的有挺强的背景,那就是软件工程学中的对象持久性问题。本文讨论的方法也是持久性的一个可能实现。废话少说,看看C++是怎么做的。
动态创建当然要用指针。我们不希望在程序中出现硬编码:
Shape * pShape = new Circle;
那就用函数/对象包装一下。函数包装简单、高效一些。于是有Circle类的成员函数:
Shape *createInstance()
我们需要用这个函数去创建Circle对象,同时只有Circle对象创建以后才可以调用createInstance()。这是一对矛盾。解决的办法是将createInstance()写成静态函数。于是Shpae和Circle类长的是这个样子:
class Shape{
public:
virtual void Draw() = 0;
};
class Circle : public Shape{
public:
void Draw()
static Shape *createInstance()
};
指向createInstance()的函数指针当然要放在工厂类里面,同时要和创建对象的名字关联起来。std::map的键-值组合当然是最佳人选。我们还有如下定义:
typedef Shape *(*FactoryFunction)();
FactoryFunction就是一个指向函数的指针,而这个函数返回一个Shape *类型。如果你对FactoryFunction有疑问,可以看看关于函数指针的书。
有了FactoryFunction,我们来定义工厂类中的map:
std::map<std::string,FactoryFunction> m_FactoryFunctions;
这样的map对象是把名字(string类型)和函数指针作为一个组合。向工厂登记产品的时候,函数可以写成这样:
void ShapeFactory::Register(std::string name, FactoryFunction instanceFunction){
m_FactoryFunctions[name] = instanceFunction;
}
登记Circle类的创建函数:
Register("circle", &Circle::createInstance);
ShapeFactory类中由名字得到函数指针可以写成这样:
Shape * ShapeFactory::getInstance(std::string name) {
if (m_FactoryFunctions.count(name)) return m_FactoryFunctions[name]();
else return NULL;
}
map.count()的用法可以看看STL的手册。ShapeFacoty的完整定义如下:
class ShapeFactory
{
public:
static void Register(std::string name, FactoryFunction instanceFunction)
static Shape * getInstance (std::string name)
{ if (m_FactoryFunctions.count(name)) return m_FactoryFunctions[name]();
else return NULL; }
private:
static std::map<std::string,FactoryFunction> m_FactoryFunctions;
};
std::map<std::string,FactoryFunction> ShapeFactory::m_FactoryFunctions;
其中Register()和getInstance()是静态函数,m_FactoryFunctions是静态成员变量。之所以这样做是避免创建ShapeFactory对象,以简化编程。同时便于用单例(Singleton)模式实现ShapeFactory。
这些完成以后,就可以向工厂登记产品和使用产品了。看看下面代码:
void someFunction()
{
ShapeFactory::Register("circle", & Circle::createInstance);
Shape * pShape = NULL;
pShape = ShapeFactory::getInstance("circle");
if (NULL == pShape)
{
std::cout << "can't find the product in the factory" << std::endl;
delete pShape;
}
else
{
pShape->Draw();
delete pShape;
}
}
怎么样,还不算复杂吧。有人会认为函数指针太不“面向对象”了,应该用其他方法。工厂模式的实现方法很多的,这里只是做一个例子。
注意观察上面的代码。这里已经没有了switch/case语句。而且ShapeFactory类和Shape类的定义和实现是完全封闭的。新添加的产品类将不改变工厂类和基类的代码。这是一个标志性的变化。当用户添加一个新产品的时候,他可以不管工厂已经有什么产品,也不用写丑陋的switch/case语句了。具体的产品是在运行时刻决定的。这样的工厂模式才具备实用价值,而不是简单的、对象/类之间的堆砌和关联。
当我们添加一个新产品的时候,比如Triangle,可以这样做:
1) 定义Triangle类:
class Triangle : public Shape
{
public:
void Draw()
static Shape *createInstance()
};
2) 在合适的地方登记Triangle:
ShapeFactory::Register("triangle", & Triangle::createInstance);
3) 给出名字,就可以创建Triangle对象了:
pShape = ShapeFactory::getInstance("triangle");
综上所述,我们的第一个目标已经实现:由名字创建对象。同时我们也详细讨论的工厂模式的实现。注意我们的工厂只是一个ConcreteFactory。在上面的新产品添加过程中,能不能省略第二步?能否实现产品在工厂中的自动登记?这将是我们要讨论的第二个问题。
目前的实现对JAVA和PYTHON程序员来说不是难事。但是产品自动登记可就不简单了。有谁可以在1周内做出来,我就转行学JAVA和PYTHON。
最后贴出完整代码(VC6和GCC都编译通过):
#pragma warning (disable:4786)
#include <iostream>
#include <map>
#include <string>
class Shape;
typedef Shape *(*FactoryFunction)();
class ShapeFactory
{
public:
static void Register(std::string name, FactoryFunction instanceFunction)
static Shape * getInstance(std::string name)
{ if (m_FactoryFunctions.count(name)) return m_FactoryFunctions[name]();
else return NULL; }
private:
static std::map<std::string,FactoryFunction> m_FactoryFunctions;
};
std::map<std::string,FactoryFunction> ShapeFactory::m_FactoryFunctions;
class Shape
{
public:
virtual void Draw() = 0;
};
class Circle : public Shape
{
public:
void Draw()
static Shape *createInstance()
};
class Triangle : public Shape
{
public:
void Draw()
static Shape *createInstance()
};
int main()
{
ShapeFactory::Register("circle", & Circle::createInstance);
ShapeFactory::Register("Triangle", & Triangle::createInstance);
Shape * pShape = NULL;
pShape = ShapeFactory::getInstance("circle");
if (NULL == pShape)
{
std::cout << "can't find the product in the factory" << std::endl;
delete pShape;
}
else
{
pShape->Draw();
delete pShape;
}
return 0;
}