如何正確實現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). 工廠方法可能更接近你所看到的,雖然它仍然有點不同(至少如果內存服務,它需要你在工廠創建該類型的對象之前註冊一個類型)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章