如何正确地实现C ++中的工厂方法模式 [英] How to implement the factory method pattern in C++ correctly

查看:93
本文介绍了如何正确地实现C ++中的工厂方法模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



如何正确地在C ++中实现Factory Method?



目标:使客户端能够实例化一些对象使用工厂方法而不是对象的构造函数,没有不可接受的后果和性能命中。



通过工厂方法模式,我的意思是对象内的静态工厂方法或另一个类中定义的方法或全局函数。一般来说,将X类实例化的正常方式重定向到除构造函数以外的其他任何地方的概念。



让我浏览一些可能的答案,我想到






0)不要制造工厂,制作构造器。



这听起来不错(通常是最好的解决方案),但不是一般的补救措施。首先,有些情况下,对象构造是一个任务复杂到足以证明它提取到另一个类。但是即使将这个事实放在一边,即使是使用构造函数的简单对象也不会做。



我知道的最简单的例子是2-D Vector类。这么简单,但棘手。我想要能够从笛卡尔坐标和极坐标两者构造它。显然,我不能做:

  struct Vec2 {
Vec2(float x,float y);
Vec2(浮角,浮动幅度); //不是有效的超载!
// ...
};那么我自然的想法就是这样:





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

其中,而不是构造函数导致我使用静态工厂方法...这本质上意味着我以某种方式实施了工厂模式(该类成为自己的工厂)。这看起来不错(并且适合这种特殊情况),但在某些情况下会失败,我将在第2点进行描述。请阅读。








1)Java Way



Java简单,因为我们只有动态分配的对象。制作一个工厂是微不足道的:

  class FooFactory {
public Foo createFooInSomeWay(){
/ /可以是一个静态方法,
//如果我们不需要工厂提供自己的对象语义
//并且只是作为一组方法
返回新的Foo (一些,args);
}
}

在C ++中,这转换为:

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

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



无论如何,除了哲学:在一般情况下,我不想强​​制工厂的用户被限制动态分配。






2)返回值



好的,所以我们知道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中,有保证的拷贝精灵,对于上述代码,不可复制是没有问题的)



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






3)两阶段构建



有人可能想出的另一件事是分离对象分配问题它的初始化。这通常导致如下代码:

  class Foo {
public:
Foo(){
//空或几乎空
}
// ...
};

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

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

人们可能认为它的工作原理像魅力。我们在代码中支付的唯一代价...



由于我已经写了所有这一切,将其作为最后一个,我也不喜欢。 :)为什么?



首先,我真诚地不喜欢两阶段建设的概念,当我使用它时,我感到内疚。如果我设计我的对象,断言如果存在,它处于有效状态,我觉得我的代码更安全,更容易出错。我喜欢这样的方式。



不得不放弃这个约定,改变我的对象的设计只是为了使它的工厂的目的是...很好,笨重。 / p>

我知道上述不会说服许多人,所以让我给一些更坚实的论据。使用两阶段构造,您不能:




  • initialise const 或参考成员变量,

  • 将参数传递给基类构造函数和成员对象构造函数。



是一些我现在不能想到的一些缺点,我甚至不觉得特别有义务,因为上面的项目符号已经说服了我。



所以:甚至没有一个很好的一般解决方案来实施工厂。






结论:



我们希望有一种对象实例化的方法,它将:




  • 允许统一的实例化,无论分配, li>
  • 给施工方法提供不同的有意义的名称(因此不依赖于参数重载),

  • 不会引入显着的性能打击,最好是重要的代码膨胀,特别是在客户端,

  • <我们相信我已经证明了我所提到的方式,我认为我已经证明了这样的一种方式,因为可能被引入任何类。


<不符合这些要求。



任何提示?请给我一个解决方案,我不想认为这种语言不会允许我正确地实现这样一个简单的概念。

解决方案


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

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

  Vec2(float x,float y); 
Vec2(浮角,浮动幅度); //不是有效的超载!

有一个简单的解决方法:

  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工厂{
public:
摘要* create();
};

工厂f;
摘要* a = f.create();
a-> do();

在其他情况下,工厂只是帮助解决轻微的问题,如您提到的重载。如果可以以统一的方式使用它们,那将是很好的,但是它并不会伤害太多,这可能是不可能的。


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:

How do I implement Factory Method in C++ correctly?

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".

Let me skim through some possible answers which I have thought of.


0) Don't make factories, make constructors.

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. 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.

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.


1) The Java Way

Java has it simple, as we only have dynamic-allocated objects. 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:

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. 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.

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

OK, so we know that 1) is cool when we want dynamic allocation. 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++. So if Foo is expensive to copy, this approach is risky.

And what if Foo is not copiable at all? Well, doh. (Note that in C++17 with guaranteed copy elision, not-being-copiable is no problem anymore for the code above)

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.


3) Two-phase construction

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,
  • 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.

解决方案

First of all, there are cases when object construction is a task complex enough to justify its extraction to another class.

I believe this point is incorrect. The complexity doesn't really matter. The relevance is what does. If an object can be constructed in one step (not like in the builder pattern), the constructor is the right place to do it. If you really need another class to perform the job, then it should be a helper class that is used from the constructor anyway.

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

There is an easy workaround for this:

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

The only disadvantage is that it looks a bit verbose:

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

But the good thing is that you can immediately see what coordinate type you're using, and at the same time you don't have to worry about copying. If you want copying, and it's expensive (as proven by profiling, of course), you may wish to use something like Qt's shared classes to avoid copying overhead.

As for the allocation type, the main reason to use the factory pattern is usually polymorphism. Constructors can't be virtual, and even if they could, it wouldn't make much sense. When using static or stack allocation, you can't create objects in a polymorphic way because the compiler needs to know the exact size. So it works only with pointers and references. And returning a reference from a factory doesn't work too, because while an object technically can be deleted by reference, it could be rather confusing and bug-prone, see Is the practice of returning a C++ reference variable, evil? for example. So pointers are the only thing that's left, and that includes smart pointers too. In other words, factories are most useful when used with dynamic allocation, so you can do things like this:

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

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

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

In other cases, factories just help to solve minor problems like those with overloads you have mentioned. It would be nice if it was possible to use them in a uniform way, but it doesn't hurt much that it is probably impossible.

这篇关于如何正确地实现C ++中的工厂方法模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆