如何正确实现C ++中的工厂方法模式

时间:2011-02-25 17:52:02

标签: c++ design-patterns idioms factory-method

11 个答案:

答案 0 :(得分:97)

  

首先,有时会出现这种情况   对象构造是一项任务复杂   足以证明其提取的合理性   另一堂课。

我认为这一点是不正确的。复杂性并不重要。相关性是什么。如果一个对象可以在一个步骤中构建(不像构建器模式中那样),那么构造函数就是正确的位置。如果你真的需要另一个类来执行这个工作,那么它应该是一个从构造函数中使用的辅助类。

Vec2(float x, float y);
Vec2(float angle, float magnitude); // not a valid overload!

有一个简单的解决方法:

struct Cartesian {
  inline Cartesian(float x, float y): x(x), y(y) {}
  float x, y;
};
struct Polar {
  inline Polar(float angle, float magnitude): angle(angle), magnitude(magnitude) {}
  float angle, magnitude;
};
Vec2(const Cartesian &cartesian);
Vec2(const Polar &polar);

唯一的缺点是它看起来有点冗长:

Vec2 v2(Vec2::Cartesian(3.0f, 4.0f));

但好处是你可以立即看到你正在使用的坐标类型,同时你不必担心复制。如果你想要复制,而且它很昂贵(当然通过分析证明),你可能希望使用类似Qt's shared classes的东西来避免复制开销。

对于分配类型,使用工厂模式的主要原因通常是多态。构造函数不能是虚拟的,即使它们可以,也没有多大意义。使用静态或堆栈分配时,无法以多态方式创建对象,因为编译器需要知道确切的大小。所以它只适用于指针和引用。从工厂返回引用也不起作用,因为虽然技术上可以通过引用删除对象,但它可能相当混乱且容易出错,例如,请参阅Is the practice of returning a C++ reference variable, evil?。所以指针是唯一剩下的东西,包括智能指针。换句话说,工厂在与动态分配一起使用时最有用,所以你可以这样做:

class Abstract {
  public:
    virtual void do() = 0;
};

class Factory {
  public:
    Abstract *create();
};

Factory f;
Abstract *a = f.create();
a->do();

在其他情况下,工厂只是帮助解决一些小问题,比如你提到的超载问题。如果可以以统一的方式使用它们会很好,但它可能不会造成太大的伤害。

答案 1 :(得分:45)

简单工厂示例:

// Factory returns object and ownership
// Caller responsible for deletion.
#include <memory>
class FactoryReleaseOwnership{
  public:
    std::unique_ptr<Foo> createFooInSomeWay(){
      return std::unique_ptr<Foo>(new Foo(some, args));
    }
};

// Factory retains object ownership
// Thus returning a reference.
#include <boost/ptr_container/ptr_vector.hpp>
class FactoryRetainOwnership{
  boost::ptr_vector<Foo>  myFoo;
  public:
    Foo& createFooInSomeWay(){
      // Must take care that factory last longer than all references.
      // Could make myFoo static so it last as long as the application.
      myFoo.push_back(new Foo(some, args));
      return myFoo.back();
    }
};

答案 2 :(得分:37)

您是否考虑过根本不使用工厂,而是充分利用类型系统?我可以想到两种不同的方法来做这种事情:

选项1:

struct linear {
    linear(float x, float y) : x_(x), y_(y){}
    float x_;
    float y_;
};

struct polar {
    polar(float angle, float magnitude) : angle_(angle),  magnitude_(magnitude) {}
    float angle_;
    float magnitude_;
};


struct Vec2 {
    explicit Vec2(const linear &l) { /* ... */ }
    explicit Vec2(const polar &p) { /* ... */ }
};

让你可以写下这样的内容:

Vec2 v(linear(1.0, 2.0));

选项2:

你可以像STL一样使用“标签”和迭代器等。例如:

struct linear_coord_tag linear_coord {}; // declare type and a global
struct polar_coord_tag polar_coord {};

struct Vec2 {
    Vec2(float x, float y, const linear_coord_tag &) { /* ... */ }
    Vec2(float angle, float magnitude, const polar_coord_tag &) { /* ... */ }
};

第二种方法允许您编写如下代码:

Vec2 v(1.0, 2.0, linear_coord);

这也是很好的和富有表现力的,同时允许你为每个构造函数提供独特的原型。

答案 3 :(得分:26)

您可以在http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

中阅读非常好的解决方案

最佳解决方案是“评论和讨论”,请参阅“不需要静态创建方法”。

从这个想法,我做了一个工厂。请注意,我正在使用Qt,但您可以为std等效项更改QMap和QString。

#ifndef FACTORY_H
#define FACTORY_H

#include <QMap>
#include <QString>

template <typename T>
class Factory
{
public:
    template <typename TDerived>
    void registerType(QString name)
    {
        static_assert(std::is_base_of<T, TDerived>::value, "Factory::registerType doesn't accept this type because doesn't derive from base class");
        _createFuncs[name] = &createFunc<TDerived>;
    }

    T* create(QString name) {
        typename QMap<QString,PCreateFunc>::const_iterator it = _createFuncs.find(name);
        if (it != _createFuncs.end()) {
            return it.value()();
        }
        return nullptr;
    }

private:
    template <typename TDerived>
    static T* createFunc()
    {
        return new TDerived();
    }

    typedef T* (*PCreateFunc)();
    QMap<QString,PCreateFunc> _createFuncs;
};

#endif // FACTORY_H

样本用法:

Factory<BaseClass> f;
f.registerType<Descendant1>("Descendant1");
f.registerType<Descendant2>("Descendant2");
Descendant1* d1 = static_cast<Descendant1*>(f.create("Descendant1"));
Descendant2* d2 = static_cast<Descendant2*>(f.create("Descendant2"));
BaseClass *b1 = f.create("Descendant1");
BaseClass *b2 = f.create("Descendant2");

答案 4 :(得分:15)

我大多同意接受的答案,但是现有答案中没有涉及C ++ 11选项:

  • 按值和
  • 返回工厂方法结果
  • 提供便宜的移动构造函数

示例:

struct sandwich {
  // Factory methods.
  static sandwich ham();
  static sandwich spam();
  // Move constructor.
  sandwich(sandwich &&);
  // etc.
};

然后你可以在堆栈上构建对象:

sandwich mine{sandwich::ham()};

作为其他事物的子对象:

auto lunch = std::make_pair(sandwich::spam(), apple{});

或动态分配:

auto ptr = std::make_shared<sandwich>(sandwich::ham());

我什么时候可以使用它?

如果在公共构造函数中,如果没有一些初步计算,就不可能为所有类成员提供有意义的初始化器,那么我可能会将该构造函数转换为静态方法。静态方法执行初步计算,然后通过私有构造函数返回值结果,该构造函数只执行成员初始化。

我说'可能'因为它取决于哪种方法能够提供最清晰的代码而不会产生不必要的低效率。

答案 5 :(得分:11)

Loki同时拥有Factory MethodAbstract Factory。两者都在Andei Alexandrescu的 Modern C ++ Design 中进行了详细记录。工厂方法可能更接近你所看到的,虽然它仍然有点不同(至少如果内存服务,它需要你在工厂创建该类型的对象之前注册一个类型)。

答案 6 :(得分:5)

我不会试图回答我的所有问题,因为我认为它太宽泛了。只是几个笔记:

  

有些情况下,对象构造是一个复杂的任务,足以证明它被提取到另一个类。

该类实际上是Builder,而不是工厂。

  

在一般情况下,我不想强​​迫工厂用户限制动态分配。

然后你可以让你的工厂将它封装在一个智能指针中。我相信这样你就可以吃蛋糕了。

这也消除了与按价值返回相关的问题。

  

结论:通过返回对象来建立工厂确实是某些情况的解决方案(例如前面提到的2-D向量),但仍然不是构造函数的一般替代。

事实上。所有设计模式都有其(语言特定的)约束和缺点。建议仅在它们帮助您解决问题时使用它们,而不是为了它们自己。

如果您正在完成“完美”的工厂实施,那么,祝您好运。

答案 7 :(得分:2)

工厂模式

class Point
{
public:
  static Point Cartesian(double x, double y);
private:
};

如果你的编译器不支持返回值优化,那就放弃它,它可能根本不包含很多优化......

答案 8 :(得分:1)

我知道这个问题已在3年前得到解答,但这可能就是你所寻找的。 谷歌几周前发布了一个库,允许轻松灵活的动态对象分配。这是:http://google-opensource.blogspot.fr/2014/01/introducing-infact-library.html

答案 9 :(得分:1)

这是我的c ++ 11风格解决方案。参数'base'用于所有子类的基类。创建者,是std :: function对象来创建子类实例,可能是绑定到你的子类'静态成员函数'create(some args)'。这可能不完美但对我有用。这是一种'通用'解决方案。

template <class base, class... params> class factory {
public:
  factory() {}
  factory(const factory &) = delete;
  factory &operator=(const factory &) = delete;

  auto create(const std::string name, params... args) {
    auto key = your_hash_func(name.c_str(), name.size());
    return std::move(create(key, args...));
  }

  auto create(key_t key, params... args) {
    std::unique_ptr<base> obj{creators_[key](args...)};
    return obj;
  }

  void register_creator(const std::string name,
                        std::function<base *(params...)> &&creator) {
    auto key = your_hash_func(name.c_str(), name.size());
    creators_[key] = std::move(creator);
  }

protected:
  std::unordered_map<key_t, std::function<base *(params...)>> creators_;
};

关于使用的一个例子。

class base {
public:
  base(int val) : val_(val) {}

  virtual ~base() { std::cout << "base destroyed\n"; }

protected:
  int val_ = 0;
};

class foo : public base {
public:
  foo(int val) : base(val) { std::cout << "foo " << val << " \n"; }

  static foo *create(int val) { return new foo(val); }

  virtual ~foo() { std::cout << "foo destroyed\n"; }
};

class bar : public base {
public:
  bar(int val) : base(val) { std::cout << "bar " << val << "\n"; }

  static bar *create(int val) { return new bar(val); }

  virtual ~bar() { std::cout << "bar destroyed\n"; }
};

int main() {
  common::factory<base, int> factory;

  auto foo_creator = std::bind(&foo::create, std::placeholders::_1);
  auto bar_creator = std::bind(&bar::create, std::placeholders::_1);

  factory.register_creator("foo", foo_creator);
  factory.register_creator("bar", bar_creator);

  {
    auto foo_obj = std::move(factory.create("foo", 80));
    foo_obj.reset();
  }

  {
    auto bar_obj = std::move(factory.create("bar", 90));
    bar_obj.reset();
  }
}

答案 10 :(得分:0)

extern std::pair<std::string_view, Base*(*)()> const factories[2];

decltype(factories) factories{
  {"blah", []() -> Base*{return new Blah;}},
  {"foo", []() -> Base*{return new Foo;}}
};