如何在c++中正确地实现工厂方法模式

c++中有一件事一直让我感到不舒服,因为我真的不知道该怎么做,尽管它听起来很简单:

我如何在c++中正确地实现工厂方法?

目标:允许客户端使用工厂方法而不是对象的构造函数实例化一些对象,而不会造成不可接受的后果和性能损失。

通过“工厂方法模式”,我指的是对象中的静态工厂方法或定义在另一个类中的方法,或全局函数。只是一般的“将类X实例化的正常方式重定向到构造函数以外的任何地方的概念”。

让我粗略地看一下我想到的一些可能的答案。


0)不要创建工厂,而是创建构造函数。

这听起来不错(实际上通常是最好的解决方案),但不是万能的补救措施。首先,在某些情况下,对象构造是一项非常复杂的任务,需要将其提取到另一个类。但即使把这个事实放在一边,即使对于简单的对象,只使用构造函数通常也不行。

我所知道的最简单的例子是2-D Vector类。如此简单,却又棘手。我想用笛卡尔坐标和极坐标来构造它。显然,我不能:

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

我的自然思维方式是:

struct Vec2 {
static Vec2 fromLinear(float x, float y);
static Vec2 fromPolar(float angle, float magnitude);
// ...
};

这,而不是构造函数,导致我使用静态工厂方法…这本质上意味着我正在以某种方式实现工厂模式(“类变成了它自己的工厂”)。这看起来很好(适合这个特定的情况),但在某些情况下会失败,我将在第2点中描述这一点。请继续读下去。

另一种情况:试图通过某些API的两个不透明的类型定义重载(比如不相关域的GUID,或者一个GUID和一个位字段),类型在语义上完全不同(理论上是有效的重载),但实际上是同一件事——比如无符号整型或空指针。


1) Java方式

Java很简单,因为我们只有动态分配的对象。建造工厂就像:

class FooFactory {
public Foo createFooInSomeWay() {
// can be a static method as well,
//  if we don't need the factory to provide its own object semantics
//  and just serve as a group of methods
return new Foo(some, args);
}
}

在c++中,这转换为:

class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
};

很酷?通常,的确。但是,这迫使用户只能使用动态分配。静态分配使c++变得复杂,但也常常使它变得强大。此外,我认为存在一些目标(关键字:嵌入式)不允许动态分配。这并不意味着这些平台的用户喜欢编写干净的OOP。

不管怎样,抛开哲学不谈:在一般情况下,我不想强迫工厂的用户受限于动态分配。


2) Return-by-value

好的,我们知道当我们需要动态分配时,1)很酷。为什么不在此基础上增加静态分配呢?

class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooInSomeWay() {
return Foo(some, args);
}
};

什么?我们不能通过返回类型重载?哦,我们当然不能。因此,让我们改变方法名来反映这一点。是的,我写了上面的无效代码示例,只是为了强调我是多么不喜欢更改方法名称,例如,因为我们现在不能正确地实现与语言无关的工厂设计,因为我们必须更改名称——而且这段代码的每个用户都需要记住实现与规范的区别。

class FooFactory {
public:
Foo* createDynamicFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooObjectInSomeWay() {
return Foo(some, args);
}
};

好吧……好了。它很难看,因为我们需要更改方法名。这是不完美的,因为我们需要编写两次相同的代码。但一旦完成,它就会起作用。对吧?

嗯,通常。但有时并非如此。当创建Foo时,我们实际上依赖于编译器为我们做返回值优化,因为c++标准足够友好,编译器供应商不指定什么时候在原地创建对象,什么时候在c++中按值返回临时对象时复制它。因此,如果复制Foo的代价很高,这种方法就有风险。

如果Foo根本不可复制呢?嗯,哎。(注意,在保证复制省略的c++ 17中,对于上面的代码来说,不可复制不再是问题)

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


3)两阶段施工

有人可能会想到的另一件事是将对象分配问题与其初始化问题分开。这通常会导致如下代码:

class Foo {
public:
Foo() {
// empty or almost empty
}
// ...
};


class FooFactory {
public:
void createFooInSomeWay(Foo& foo, some, args);
};


void clientCode() {
Foo staticFoo;
auto_ptr<Foo> dynamicFoo = new Foo();
FooFactory factory;
factory.createFooInSomeWay(&staticFoo);
factory.createFooInSomeWay(&dynamicFoo.get());
// ...
}

有人可能会认为它很有魔力。我们在代码中付出的唯一代价…

既然我写了所有这些,并把这篇作为最后一篇,我一定也不喜欢它。:为什么?

首先……我真的不喜欢两阶段结构的概念,当我使用它时,我感到内疚。如果我用“如果它存在,它就处于有效状态”的断言来设计我的对象,我觉得我的代码更安全,更不容易出错。我喜欢那样。

不得不放弃这个惯例,改变我的对象的设计,只是为了制造工厂的目的。嗯,笨拙。

我知道以上这些不能说服很多人,所以让我来给出一些更有说服力的论据。使用两阶段结构,你不能:

  • 初始化const或引用成员变量,
  • 将参数传递给基类构造函数和成员对象构造函数。

可能还会有一些我现在想不到的缺点,我甚至不觉得有什么特别的义务,因为上面的要点已经说服了我。

所以:对于实现工厂来说,甚至还没有一个好的通用解决方案。


结论:

我们希望有一种对象实例化的方式:

  • 无论分配如何,都允许统一的实例化,
  • 给构造方法取不同的、有意义的名字(这样就不依赖于参数重载),
  • 不要带来严重的性能损失,最好是严重的代码膨胀,尤其是在客户端,
  • 是一般的,例如:可以为任何类引入。

我相信我已经证明了我所提到的方法并不能满足这些要求。

有提示吗?请给我一个解决方案,我不想认为这个语言不能让我正确地实现这样一个微不足道的概念。

215125 次浏览

洛基有工厂方法抽象工厂。Andei Alexandrescu在现代c++设计中(广泛地)记录了这两者。factory方法可能更接近于您所追求的,尽管它仍然有一点不同(至少如果内存正常的话,它要求您在工厂创建该类型的对象之前注册一个类型)。

我不想回答我所有的问题,因为我认为这太宽泛了。只是一些注释:

在某些情况下,对象构造是一项非常复杂的任务,需要将其提取到另一个类。

这个类实际上是构建器,而不是Factory。

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

然后你可以让你的工厂把它封装在一个智能指针中。我相信这样你可以鱼与熊掌兼得。

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

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

确实。所有设计模式都有其(特定于语言的)约束和缺陷。建议只在它们帮助您解决问题时使用它们,而不是为了它们本身。

如果你追求的是“完美”的工厂实施,那么,祝你好运。

首先,有些情况下 对象构造是一项复杂的任务 足以证明它的开采 另一个类。< / p >

我认为这一点是不正确的。复杂性并不重要。相关性是起作用的东西。如果一个对象可以在一个步骤中构造(不像在构造器模式中那样),构造函数就是正确的地方。如果确实需要另一个类来执行任务,那么它应该是一个从构造函数中使用的helper类。

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的共享类的东西来避免复制开销。

至于分配类型,使用工厂模式的主要原因通常是多态性。构造函数不能是虚拟的,即使可以,也没有多大意义。当使用静态分配或堆栈分配时,您不能以多态方式创建对象,因为编译器需要知道确切的大小。所以它只对指针和引用有效。而且从工厂返回引用也不行,因为虽然从技术上讲可以对象可以通过引用删除,但它可能相当混乱且容易出错,例如参见返回c++引用变量的做法是邪恶的吗?。指针是唯一剩下的东西,这也包括智能指针。换句话说,工厂在与动态分配一起使用时最有用,所以你可以这样做:

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


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


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

在其他情况下,工厂只是帮助解决一些小问题,比如你提到的过载问题。如果能够以统一的方式使用它们,那就太好了,但这可能是不可能的,这也没什么坏处。

工厂模式

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

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

简单的工厂示例:

// 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();
}
};

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

选项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年前就有答案了,但这可能就是你想要的。

谷歌在几周前发布了一个库,允许简单灵活的动态对象分配。这里是:http://google-opensource.blogspot.fr/2014/01/introducing-infact-library.html

你可以读到一个很好的解决方案:http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

最好的解决方案是在“评论和讨论”中,参见“不需要静态创建方法”。

根据这个想法,我做了一个工厂。注意,我使用的是Qt,但你可以改变QMap和QString的std等价物。

#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");

我基本上同意已被接受的答案,但有一个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());

我什么时候可以用这个?

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

我之所以说“可能”,是因为它取决于哪种方法能给出最清晰的代码,而不会造成不必要的效率低下。

这是我的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();
}
}
extern std::pair<std::string_view, Base*(*)()> const factories[2];


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