什么时候在对象中使用工厂方法而不是factory类是一个好主意?

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或引用成员变量, 将参数传递给基类构造函数和成员对象构造函数。

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

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


结论:

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

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

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

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

我知道有很多关于这两种模式之间差异的帖子,但有一些东西我找不到。

From what I have been reading, I see that the factory method pattern allows you to define how to create a single concrete product but hiding the implementation from the client as they will see a generic product. My first question is about the abstract factory. Is its role to allow you to create families of concrete objects in (that can depend on what specific factory you use) rather than just a single concrete object? Does the abstract factory only return one very large object or many objects depending on what methods you call?

我最后两个问题是关于一句我在很多地方都见过的引语,我不能完全理解:

两者之间的一个区别是 使用抽象工厂模式,a 类委托的责任 对象实例化到另一个对象 通过合成,而工厂 方法模式使用继承和 类依赖于子类来处理 所需的对象实例化。

我的理解是,工厂方法模式有一个Creator接口,它将使ConcreteCreator负责知道要实例化哪个ConcreteProduct。这就是使用继承来处理对象实例化的意思吗?

现在,关于引用,抽象工厂模式是如何通过组合将对象实例化的责任委托给另一个对象的?这是什么意思?在我看来,抽象工厂模式似乎也使用继承来完成构造过程,但我仍然在学习这些模式。

任何帮助,特别是最后一个问题,将非常感激。

Builder设计模式和Factory设计模式之间的区别是什么?

哪一种更有利?为什么?

如果我想测试和比较/对比这些模式,我如何将我的发现表示为图表?