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

本文翻译自:How to implement the factory method pattern in C++ correctly

There's this one thing in C++ which has been making me feel uncomfortable for quite a long time, because I honestly don't know how to do it, even though it sounds simple: C ++中的这一件事让我感到不舒服很长一段时间,因为我老实说不知道该怎么做,尽管听起来很简单:

How do I implement Factory Method in C++ correctly? 如何正确地在C ++中实现Factory方法?

Goal: to make it possible to allow the client to instantiate some object using factory methods instead of the object's constructors, without unacceptable consequences and a performance hit. 目标:允许客户端使用工厂方法而不是对象的构造函数来实例化某个对象,而不会产生不可接受的后果和性能损失。

By "Factory method pattern", I mean both static factory methods inside an object or methods defined in another class, or global functions. “工厂方法模式”是指对象内部的静态工厂方法或另一个类中定义的方法,或全局函数。 Just generally "the concept of redirecting the normal way of instantiation of class X to anywhere else than the constructor". 通常只是“将类X的实例化的正常方式重定向到构造函数之外的任何其他位置的概念”。

Let me skim through some possible answers which I have thought of. 让我略过一些我想到过的可能答案。


0) Don't make factories, make constructors. 0)不要制造工厂,制造建造者。

This sounds nice (and indeed often the best solution), but is not a general remedy. 这听起来不错(实际上通常是最好的解决方案),但不是一般的补救措施。 First of all, there are cases when object construction is a task complex enough to justify its extraction to another class. 首先,有些情况下,对象构造是一个复杂的任务,足以证明它被提取到另一个类。 But even putting that fact aside, even for simple objects using just constructors often won't do. 但即使将这个事实放在一边,即使对于仅使用构造函数的简单对象,通常也不会这样做。

The simplest example I know is a 2-D Vector class. 我所知道的最简单的例子是2-D Vector类。 So simple, yet tricky. 这么简单,但很棘手。 I want to be able to construct it both from both Cartesian and polar coordinates. 我希望能够从笛卡尔坐标和极坐标两者构造它。 Obviously, I cannot do: 显然,我做不到:

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

My natural way of thinking is then: 我的自然思维方式是:

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

Which, instead of constructors, leads me to usage of static factory methods... which essentially means that I'm implementing the factory pattern, in some way ("the class becomes its own factory"). 其中,而不是构造函数,导致我使用静态工厂方法......这实际上意味着我正在以某种方式实现工厂模式(“类成为自己的工厂”)。 This looks nice (and would suit this particular case), but fails in some cases, which I'm going to describe in point 2. Do read on. 这看起来不错(并且适合这种特殊情况),但在某些情况下失败,我将在第2点中描述。继续阅读。

another case: trying to overload by two opaque typedefs of some API (such as GUIDs of unrelated domains, or a GUID and a bitfield), types semantically totally different (so - in theory - valid overloads) but which actually turn out to be the same thing - like unsigned ints or void pointers. 另一种情况:尝试通过某些API的两个opaque typedef(例如不相关域的GUID,或GUID和位域)重载,类型在语义上完全不同(所以 - 理论上 - 有效的重载)但实际上它们实际上是同样的事情 - 像无符号的int或void指针。


1) The Java Way 1)Java Way

Java has it simple, as we only have dynamic-allocated objects. Java很简单,因为我们只有动态分配的对象。 Making a factory is as trivial as: 制造工厂同样简单:

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

In C++, this translates to: 在C ++中,这转换为:

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

Cool? 凉? Often, indeed. 通常,确实如此。 But then- this forces the user to only use dynamic allocation. 但随后 - 这迫使用户仅使用动态分配。 Static allocation is what makes C++ complex, but is also what often makes it powerful. 静态分配是使C ++复杂化的原因,也是使其变得强大的原因。 Also, I believe that there exist some targets (keyword: embedded) which don't allow for dynamic allocation. 另外,我认为存在一些不允许动态分配的目标(关键字:嵌入式)。 And that doesn't imply that the users of those platforms like to write clean OOP. 这并不意味着这些平台的用户喜欢编写干净的OOP。

Anyway, philosophy aside: In the general case, I don't want to force the users of the factory to be restrained to dynamic allocation. 无论如何,哲学不谈:在一般情况下,我不想强​​迫工厂的用户限制动态分配。


2) Return-by-value 2)按价值返回

OK, so we know that 1) is cool when we want dynamic allocation. 好的,所以我们知道1)在我们想要动态分配时很酷。 Why won't we add static allocation on top of that? 为什么我们不在其上添加静态分配?

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

What? 什么? We can't overload by the return type? 我们不能通过返回类型重载? Oh, of course we can't. 哦,当然我们不能。 So let's change the method names to reflect that. 所以让我们改变方法名称来反映这一点。 And yes, I've written the invalid code example above just to stress how much I dislike the need to change the method name, for example because we cannot implement a language-agnostic factory design properly now, since we have to change names - and every user of this code will need to remember that difference of the implementation from the specification. 是的,我上面写的无效代码示例只是为了强调我不喜欢需要更改方法名称,例如因为我们现在无法正确实现与语言无关的工厂设计,因为我们必须更改名称 - 和此代码的每个用户都需要记住实现与规范的区别。

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

OK... there we have it. 好的......我们有它。 It's ugly, as we need to change the method name. 这很难看,因为我们需要更改方法名称。 It's imperfect, since we need to write the same code twice. 这是不完美的,因为我们需要两次编写相同的代码。 But once done, it works. 但一旦完成,它就有效。 Right? 对?

Well, usually. 嗯,通常。 But sometimes it does not. 但有时却没有。 When creating Foo, we actually depend on the compiler to do the return value optimisation for us, because the C++ standard is benevolent enough for the compiler vendors not to specify when will the object created in-place and when will it be copied when returning a temporary object by value in C++. 在创建Foo时,我们实际上依赖于编译器来为我们做返回值优化,因为C ++标准对于编译器供应商而言是足够的,不会指定对象何时就地创建以及何时在返回时复制它C ++中按值的临时对象。 So if Foo is expensive to copy, this approach is risky. 因此,如果Foo复制起来很昂贵,这种方法是有风险的。

And what if Foo is not copiable at all? 如果Foo根本不可复制怎么办? Well, doh. 好吧,doh。 ( Note that in C++17 with guaranteed copy elision, not-being-copiable is no problem anymore for the code above ) 请注意,在C ++ 17中,保证复制省略,对于上面的代码,不可复制不再是问题

Conclusion: Making a factory by returning an object is indeed a solution for some cases (such as the 2-D vector previously mentioned), but still not a general replacement for constructors. 结论:通过返回对象来建立工厂确实是某些情况的解决方案(例如前面提到的2-D向量),但仍然不是构造函数的一般替代。


3) Two-phase construction 3)两相结构

Another thing that someone would probably come up with is separating the issue of object allocation and its initialisation. 有人可能想出的另一件事是分离对象分配和初始化的问题。 This usually results in code like this: 这通常导致代码如下:

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

One may think it works like a charm. 人们可能认为它就像一个魅力。 The only price we pay for in our code... 我们在代码中支付的唯一价格......

Since I've written all of this and left this as the last, I must dislike it too. 既然我写了所有这些并将其作为最后一个,我也必须不喜欢它。 :) Why? :)为什么?

First of all... I sincerely dislike the concept of two-phase construction and I feel guilty when I use it. 首先......我真诚地不喜欢两阶段结构的概念,当我使用它时我感到内疚。 If I design my objects with the assertion that "if it exists, it is in valid state", I feel that my code is safer and less error-prone. 如果我设置我的对象的断言“如果它存在,它处于有效状态”,我觉得我的代码更安全,更不容易出错。 I like it that way. 我喜欢这样。

Having to drop that convention AND changing the design of my object just for the purpose of making factory of it is.. well, unwieldy. 不得不放弃那个约定并改变我的对象的设计只是为了制造它的工厂是..好吧,笨拙。

I know that the above won't convince many people, so let's me give some more solid arguments. 我知道上述内容不会说服很多人,所以让我给出一些更为坚实的论据。 Using two-phase construction, you cannot: 使用两阶段构造,您不能:

  • initialise const or reference member variables, 初始化const或引用成员变量,
  • pass arguments to base class constructors and member object constructors. 将参数传递给基类构造函数和成员对象构造函数。

And probably there could be some more drawbacks which I can't think of right now, and I don't even feel particularly obliged to since the above bullet points convince me already. 可能还有一些我现在无法想到的缺点,我甚至不觉得特别有责任,因为上面的要点已经说服了我。

So: not even close to a good general solution for implementing a factory. 所以:甚至没有接近实施工厂的良好通用解决方案。


Conclusions: 结论:

We want to have a way of object instantiation which would: 我们想要一种对象实例化的方法,它将:

  • allow for uniform instantiation regardless of allocation, 允许统一实例化,无论分配如何,
  • give different, meaningful names to construction methods (thus not relying on by-argument overloading), 给构造方法赋予不同的,有意义的名称(因此不依赖于副参数重载),
  • not introduce a significant performance hit and, preferably, a significant code bloat hit, especially at client side, 没有引入显着的性能损失,并且最好是显着的代码膨胀,特别是在客户端,
  • be general, as in: possible to be introduced for any class. 是一般的,如:可能被引入任何类。

I believe I have proven that the ways I have mentioned don't fulfil those requirements. 我相信我已经证明我提到的方式不符合这些要求。

Any hints? 任何提示? Please provide me with a solution, I don't want to think that this language won't allow me to properly implement such a trivial concept. 请给我一个解决方案,我不想认为这种语言不会让我正确地实现这样一个琐碎的概念。


#1楼

参考:https://stackoom.com/question/LU92/如何正确实现C-中的工厂方法模式


#2楼

I know this question has been answered 3 years ago, but this may be what your were looking for. 我知道这个问题已在3年前得到解答,但这可能是你所寻找的。

Google has released a couple of weeks ago a library allowing easy and flexible dynamic object allocations. 谷歌几周前发布了一个库,允许简单灵活的动态对象分配。 Here it is: http://google-opensource.blogspot.fr/2014/01/introducing-infact-library.html 这是: http//google-opensource.blogspot.fr/2014/01/introducing-infact-library.html


#3楼

You can read a very good solution in: http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus 您可以在以下网址阅读非常好的解决方案: http//www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

The best solution is on the "comments and discussions", see the "No need for static Create methods". 最好的解决方案是“评论和讨论”,请参阅“不需要静态创建方法”。

From this idea, I've done a factory. 从这个想法,我做了一个工厂。 Note that I'm using Qt, but you can change QMap and QString for std equivalents. 请注意,我正在使用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

Sample usage: 样品用法:

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楼

I mostly agree with the accepted answer, but there is a C++11 option that has not been covered in existing answers: 我大多同意接受的答案,但有一个C ++ 11选项在现有答案中没有涉及:

  • Return factory method results by value , and 按值返回工厂方法结果,和
  • Provide a cheap move constructor . 提供廉价的移动构造函数

Example: 例:

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

Then you can construct objects on the stack: 然后你可以在堆栈上构造对象:

sandwich mine{sandwich::ham()};

As subobjects of other things: 作为其他事物的子对象:

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

Or dynamically allocated: 或动态分配:

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

When might I use this? 我什么时候可以用这个?

If, on a public constructor, it is not possible to give meaningful initialisers for all class members without some preliminary calculation, then I might convert that constructor to a static method. 如果在公共构造函数中,如果没有一些初步计算,就不可能为所有类成员提供有意义的初始化器,那么我可能会将该构造函数转换为静态方法。 The static method performs the preliminary calculations, then returns a value result via a private constructor which just does a member-wise initialisation. 静态方法执行初步计算,然后通过私有构造函数返回值结果,该构造函数仅执行成员初始化。

I say ' might ' because it depends on which approach gives the clearest code without being unnecessarily inefficient. 我说' 可能 ',因为它取决于哪种方法提供最清晰的代码而不会产生不必要的低效率。


#5楼

This is my c++11 style solution. 这是我的c ++ 11风格解决方案。 parameter 'base' is for base class of all sub-classes. 参数'base'用于所有子类的基类。 creators, are std::function objects to create sub-class instances, might be a binding to your sub-class' static member function 'create(some args)'. 创建者,是std :: function对象来创建子类实例,可能是绑定到你的子类'静态成员函数'create(some args)'。 This maybe not perfect but works for me. 这可能不完美但对我有用。 And it is kinda 'general' solution. 它有点'一般'的解决方案。

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_;
};

An example on usage. 关于使用的一个例子。

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

#6楼

Loki has both a Factory Method and an Abstract Factory . Loki有工厂方法抽象工厂 Both are documented (extensively) in Modern C++ Design , by Andei Alexandrescu. 两者都在Andei Alexandrescu的Modern C ++ Design中进行了详细记录。 The factory method is probably closer to what you seem to be after, though it's still a bit different (at least if memory serves, it requires you to register a type before the factory can create objects of that type). 工厂方法可能更接近你所看到的,虽然它仍然有点不同(至少如果内存服务,它需要你在工厂创建该类型的对象之前注册一个类型)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值