我相信Joshua Bloch在他的非常好的书“ Effective Java”中首先说了它:与构造函数相比,静态工厂方法是实例化对象的首选方法。 我不同意。 不仅因为我相信静态方法是纯粹的邪恶,而且主要是因为在这种特殊情况下,它们伪装成好的方法,使我们认为我们必须爱上它们。
让我们从面向对象的角度分析推理并查看其原因。
这是一个具有一个主要构造函数和两个次要构造函数的类:
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);}
}
你更喜欢哪一个?
据约书亚布洛赫,但使用静态工厂方法而不是构造函数(一共设置了四个,但第四个是不是适用于Java的三个基本优势了 ):
- 他们有名字。
- 他们可以缓存。
- 它们可以是子类型。
我认为,如果设计错误,那么这三者都是完全合理的。 它们是解决方法的好借口。 让我们一一介绍。
他们有名字
这是使用构造函数制作红色番茄颜色对象的方法:
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);
见,约书亚,没有静态方法,没有静态属性。
他们可以亚型
假设我们的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;}@Overridepublic 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);}
}
如果要求使用真正的红色 ,我们将返回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;}@Overridepublic 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颜色,则需要进一步改进 ,但是我希望您能理解。 逻辑必须保留在类内部 ,而不是外部,静态工厂方法甚至其他补充类中。 当然,我在说的是属于这个特定类的逻辑。 如果与类实例的管理有关,那么可以有容器和存储,就像上面的上一个示例一样。
总而言之,我强烈建议您不要使用静态方法,尤其是当它们要替换对象构造函数时。 通过其构造函数生成对象是任何面向对象软件中最 “神圣”的时刻,请不要错过它的美丽。
您可能还会发现这些相关的帖子很有趣: 每个私有静态方法都是新类的候选人 ; 您是一个更好的建筑师,您的图表更简单 ; 只有一个主要的建设者 ; 为什么InputStream设计错误 ; 为什么在OOP中很多退货声明是个坏主意 ;
翻译自: https://www.javacodegeeks.com/2017/11/constructors-static-factory-methods.html