Constructors or Static Factory Methods?

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

我相信Joshua Bloch在他的非常好的书《Effective Java》中首次提到:与构造函数相比,静态工厂方法是实例化对象的首选方式。我不同意。不仅仅是因为我认为静态方法是纯粹的邪恶,而且主要是因为在这种特定情况下,它们假装是好的,让我们误以为我们必须喜欢它们。

让我们从面向对象的角度来分析这个推理,看看为什么是错误的。

这是一个具有一个主要构造函数和两个次要构造函数的类:

这是一个类似的类,具有三个静态工厂方法:

你更喜欢哪一个?

根据Joshua Bloch的说法,使用静态工厂方法而不是构造函数有三个基本优势(实际上有四个,但第四个对Java不适用)。

  • They can cache.

  • They can subtype.

我相信这三个都是有道理的……如果设计是错误的话。它们是绕过问题的好借口。我们一个一个来看。

这是使用构造函数创建红色番茄颜色对象的方法。

这是使用静态工厂方法的方式:

似乎makeFromPalette()比仅new Color()在语义上更丰富,对吗?是的。如果我们只是将这三个数字传递给构造函数,谁知道这些数字代表什么意思呢?但是,“palette”一词可以帮助我们立即弄清楚一切。

然而,正确的解决方案应该是使用多态性和封装,将问题分解为几个语义丰富的类:

现在,我们使用正确的类的正确构造函数:

See, Joshua?

They Can Cache

假设我需要在应用程序的多个地方使用红色番茄颜色。

将创建两个对象,这显然是低效的,因为它们是相同的。最好将第一个实例保存在内存中,并在第二次调用时返回它。静态工厂方法可以解决这个问题。

然后,在Color内部的某个地方,我们保留一个私有的静态Map,其中包含所有已经实例化的对象。

它在性能方面非常有效。对于像我们的“Color”这样的小对象,问题可能不太明显,但是当对象变得更大时,它们的实例化和垃圾回收可能会浪费很多时间。

然而,有一种面向对象的方法可以解决这个问题。我们只需要引入一个新的类Palette,它成为了一个颜色存储器。

现在,我们只需创建一个Palette的实例,并在每次需要颜色时向它请求即可。

看,Joshua,没有静态方法,没有静态属性。

假设我们的类Color有一个方法lighter(),它应该将颜色转换为下一个可用的较浅颜色:

然而,有时更希望通过一组可用的Pantone颜色选择下一个较浅的颜色。

然后,我们创建一个静态工厂方法,该方法将决定哪种Color实现对我们最合适。

如果需要真正的红色,我们将返回一个PantoneColor的实例。在所有其他情况下,它只是一个标准的RGBColor。这个决定是由静态工厂方法做出的。这是我们将如何调用它的方式:

不可能通过构造函数进行相同的“分叉”,因为它只能返回声明它的类。静态方法具有所有必要的自由来返回Color的任何子类型。

然而,在面向对象的世界中,我们可以并且必须以完全不同的方式进行操作。首先,我们将Color定义为一个接口:

接下来,我们将把这个决策过程移动到自己的类Colors中,就像我们在之前的示例中所做的一样。

而且我们将使用Colors类的实例,而不是在Color内部使用静态工厂方法。

然而,这仍然不是真正的面向对象的思维方式,因为我们将决策从它所属的对象中剥离出来。通过静态工厂方法make()或一个新的类Colors—无论如何都无关紧要—我们将对象分成两部分。第一部分是对象本身,第二部分是决策算法,它留在其他地方。

一个更加面向对象的设计是将逻辑放入PantoneColor类的对象中,它将装饰原始的RGBColor对象。

然后,我们创建一个RGBColor的实例,并使用PantoneColor进行装饰。

我们要求 red 返回一种较浅的颜色,它返回的是 Pantone 调色板中的颜色,而不是仅仅在 RGB 坐标上较浅的颜色。

当然,这个例子相对简单,并且如果我们真的希望它适用于所有的 Pantone 颜色,还需要进一步改进,但我希望你能理解。逻辑必须留在类的内部,而不是在外部的某个地方,也不是在静态工厂方法中,甚至不是在其他一些补充类中。当然,我说的是属于这个特定类的逻辑。如果与类实例的管理有关,那么可以有容器和存储,就像上面的示例一样。

总结一下,我强烈建议永远不要使用静态方法,特别是当它们将替代对象构造函数时。通过构造函数创建对象是面向对象软件中最“神圣”的时刻,不要错过它的美。

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-11-17 at 14:41

sixnines availability badge   GitHub stars