我相信 Joshua Bloch 在他的好书 “Effective Java” 中首先说过:与构造函数相比,静态工厂方法是实例化对象的首选方式。我不同意。不仅因为我相信静态方法是纯粹的邪恶,而且主要是因为在这种特殊情况下,它们假装是好的,让我们认为我们必须爱它们。
Mike Judge 摘录 (2009)
让我们从面向对象的角度来分析推理,看看为什么它是错误的。
这是一个具有一个主要构造函数和两个辅助构造函数的类:
class Color { private final int hex; Color(String rgb) { this(Integer.parseInt(rgb, 16)); } Color(int red, int green, int blue) { this(red << 16 + green << 8 + blue); } Color(int h) { this.hex = h; } }
这是一个具有三个静态工厂方法的类似类:
class Color { private final int hex; static Color makeFromRGB(String rgb) { return new Color(Integer.parseInt(rgb, 16)); } static Color makeFromPalette(int red, int green, int blue) { return new Color(red << 16 + green << 8 + blue); } static Color makeFromHex(int h) { return new Color(h); } private Color(int h) { return new Color(h); } }
你更喜欢哪一个?
根据 Joshua Bloch 的说法,使用静态工厂方法而不是构造函数有三个基本优势(实际上有四个,但第四个不再适用于 Java ):
我相信这三者都非常有道理……如果设计有误的话。它们是变通办法的好借口。让我们一一介绍。
这是使用构造函数创建 red tomato 颜色对象的方法:
Color tomato = new Color(255, 99, 71);
这是使用静态工厂方法的方式:
Color tomato = Color.makeFromPalette(255, 99, 71);
似乎 makeFromPalette()
在语义上比 new Color()
更丰富,对吧?嗯,是。如果我们只是将它们传递给构造函数,谁知道这三个数字意味着什么。但是“调色板”这个词可以帮助我们立即弄清楚一切。
真的。
然而,正确的解决方案是使用多态和封装,将问题分解为几个语义丰富的类:
interface Color { } class HexColor implements Color { private final int hex; HexColor(int h) { this.hex = h; } } class RGBColor implements Color { private final Color origin; RGBColor(int red, int green, int blue) { this.origin = new HexColor( red << 16 + green << 8 + blue ); } }
现在,我们使用正确类的正确构造函数:
Color tomato = new RGBColor(255, 99, 71);
看到了吗,约书亚?
假设我需要在应用程序的多个位置使用红番茄色:
Color tomato = new Color(255, 99, 71); // ... sometime later Color red = new Color(255, 99, 71);
将创建两个对象,这显然是低效的,因为它们是相同的。最好将第一个实例保留在内存中的某个位置,并在第二个调用到达时返回它。静态工厂方法可以解决这个问题:
Color tomato = Color.makeFromPalette(255, 99, 71); // ... sometime later Color red = Color.makeFromPalette(255, 99, 71);
然后在 Color
的某个地方,我们保留了一个私有静态 Map
,其中包含所有已经实例化的对象:
class Color { private static final Map<Integer, Color> CACHE = new HashMap<>(); private final int hex; static Color makeFromPalette(int red, int green, int blue) { final int hex = red << 16 + green << 8 + blue; return Color.CACHE.computeIfAbsent( hex, h -> new Color(h) ); } private Color(int h) { return new Color(h); } }
它在性能方面非常有效。对于像我们的 Color
这样的小对象,问题可能不会那么明显,但是当对象更大时,它们的实例化和垃圾收集可能会浪费很多时间。
真的。
然而,有一种面向对象的方法可以解决这个问题。我们刚刚引入了一个新类 Palette
,它成为颜色的存储:
class Palette { private final Map<Integer, Color> colors = new HashMap<>(); Color take(int red, int green, int blue) { final int hex = red << 16 + green << 8 + blue; return this.computerIfAbsent( hex, h -> new Color(h) ); } }
现在,我们创建一个 Palette
的实例,并要求它在我们每次需要它时返回一种颜色给我们:
Color tomato = palette.take(255, 99, 71); // Later we will get the same instance: Color red = palette.take(255, 99, 71);
看,Joshua,没有静态方法,没有静态属性。
假设我们的类 Color
有一个方法 lighter()
,它应该将颜色转移到下一个可用的较浅的颜色:
class Color { protected final int hex; Color(int h) { this.hex = h; } public Color lighter() { return new Color(hex + 0x111); } }
然而,有时更希望通过一组可用的 Pantone 颜色选择下一个较浅的颜色:
class PantoneColor extends Color { private final PantoneName pantone; PantoneColor(String name) { this(new PantoneName(name)); } PantoneColor(PantoneName name) { this.pantone = name; } @Override public Color lighter() { return new PantoneColor(this.pantone.up()); } }
然后,我们创建一个静态工厂方法,它将决定哪种 Color
实现最适合我们:
class Color { private final String code; static Color make(int h) { if (h == 0xBF1932) { return new PantoneColor("19-1664 TPX"); } return new RGBColor(h); } }
如果请求 true red 颜色,我们将返回 PantoneColor
的实例。在所有其他情况下,它只是一个标准的 RGBColor
。由静态工厂方法做出决定。这就是我们如何称呼它:
Color color = Color.make(0xBF1932);
不可能对构造函数进行相同的“分叉”,因为它只能返回声明它的类。静态方法具有返回 Color
的任何子类型的所有必要自由。
真的。
然而,在面向对象的世界中,我们可以而且必须以不同的方式来做这件事。首先,我们会让 Color
成为一个接口:
interface Color { Color lighter(); }
接下来,我们将把这个决策过程移到它自己的类 Colors
中,就像我们在前面的例子中所做的那样:
class Colors { Color make(int h) { if (h == 0xBF1932) { return new PantoneColor("19-1664-TPX"); } return new RGBColor(h); } }
我们将在 Colors
中使用类 Color
的实例而不是静态工厂方法:
colors.make(0xBF1932);
然而,这仍然不是真正的面向对象的思维方式,因为我们正在将决策权从它所属的对象上移开。通过静态工厂方法 make()
或新类 Colors
——如何实现并不重要——我们将对象分成两部分。第一部分是对象本身,第二部分是留在其他地方的决策算法。
一个更面向对象的设计是将逻辑放入类 PantoneColor
的对象中,它会装饰原始的 RGBColor
:
class PantoneColor { private final Color origin; PantoneColor(Color color) { this.origin = color; } @Override public Color lighter() { final Color next; if (this.origin.hex() == 0xBF1932) { next = new RGBColor(0xD12631); } else { next = this.origin.lighter(); } return new PantoneColor(next); } )
然后,我们创建一个 RGBColor
的实例并用 PantoneColor
装饰它:
Color red = new PantoneColor( new RGBColor(0xBF1932) );
我们要求 red
返回一种较浅的颜色,它返回 Pantone 调色板中的颜色,而不是仅在 RGB 坐标中较浅的颜色:
Color lighter = red.lighter(); // 0xD12631
当然,这个例子相当原始,如果我们真的希望它适用于所有 Pantone 颜色,还需要进一步改进,但我希望你能理解。逻辑必须留在类的内部,而不是外部的某个地方,不能在静态工厂方法中,甚至不能在其他一些补充类中。当然,我说的是属于这个特定类的逻辑。如果是类实例管理相关的东西,就可以有容器和存储,就像上面的例子一样。
总而言之,我强烈建议您永远不要使用静态方法,尤其是当它们要替换对象构造函数时。通过对象的构造函数诞生一个对象,是任何面向对象软件中最“神圣”的时刻,千万不要错过它的美妙之处。
您可能还会发现这些相关的帖子很有趣:Each Private Static Method Is a Candidate for a New Class; 你的架构师越好,你的图表就越简单; 只能有一个主构造函数; 为什么InputStream的设计是错误的; 为什么许多返回语句在 OOP 中不是一个好主意;
标签2: Java教程地址:https://www.cundage.com/article/jcg-constructors-static-factory-methods.html