用C++实现简单工厂模式

简单工厂是一种简单的设计模式用于通过不同的标识创建不同的具体产品类型。

虽然不属于GoF的23种设计模式,但是它还是很有用处的。

实现

简单工厂最常见大实现如下:

Product.h 代码:

class Product
{
public:
    virtual int method() = 0;
};

Factory.h 代码:

class Product;

class Factory
{
public:
    static Product* createProduct(const std::string& name);
};

Factory.cpp 代码:

Product* Factory::createProduct(const std::string& name)
{
    if (name == "ProductType1")
        return new ProductType1;
    else if (name == "ProductType2")
        return new ProductType2;
    // ...
    else
        return NULL;
}

使用代码:

Product* p = Factory::createProduct("ProductType1");

问题

这个实现有几个问题:

  1. 当添加新产品类的时,需要修改代码,在 Factory 中添加相应的代码,这违背了开闭原则。
  2. Factory 类是接口,但是它依赖于具体产品类,这违背了面向接口编程的原则。
  3. 任何产品类的头文件发生改变的时候都会引起 Factory 类重新编译。

为了避免上面的问题,可采取注册的方式来提供具体产品类的信息。

改进实现

新的 Factory.h 代码:

#include <map>
class Product;

class Factory
{
private:
    template <typename T>
    struct construct_helper {
        static Product* create() { return new T(); }
    }


    typedef std::map<std::string, Product*(*)()> Reg;

    static Reg& getRegistry();
public:
    template <typename T>
    static bool reg(const std::string& name) {
        Reg& r = getRegistry();
        r[name] = &construct_helper<T>::create;
        return true;
    }

    static Product* createProduct(const std::string& name);
};

Factory.cpp 的代码:

Factory::Reg& Factory::getRegistry() {
    static Reg reg_;
    return reg_;
}

Product* Factory::createProduct(const std::string& name) {
    const Reg& r = getRegistry();
    Reg::const_iterator it = r.find(name);
    return (it == r.end()) ? NULL : r->second();
}

然后是在具体产品类中添加注册代码, 比如 ProductType1.cpp

#include "ProductType1.h"
#include "Factory.h"

static bool _ = Factory::reg<ProductType1>("ProductType1");

Factory.cpp 中,保存注册信息的变量 reg_ 之所以放在函数 getRegistry 内部而不是放在外面(类里或者文件作用域)的静态变量,是为了保证该变量一定是在被使用前初始化的。因为注册的时候也是利用静态变量初始化完成的,如果注册信息也放在静态变量里面:当使用注册的点的静态变量 ( ProductType1.cpp 中的 _ ) 先初始化,引发对注册信息变量的使用,那么这个时候注册信息变量 reg_ 还没有初始化。

这个改进的实现采用了先通过静态初始化来注册类,然后再通过标识创建对象的时候查找注册信息来早到相应的创建函数。

该实现可进一步将具体产品类的函数声明为私有,并且通过友元对 Factory 类开放。