构造函数还是静态工厂方法?
我相信Joshua Bloch在他的非常好的書“ Effective Java”中首先說了它:與構造函數相比,靜態工廠方法是實例化對象的首選方法。 我不同意。 不僅因為我相信靜態方法是純粹的邪惡,而且主要是因為在這種特殊情況下,它們偽裝成好的方法,使我們認為我們必須愛上它們。
摘錄(2009),作者:麥克·賈奇(Mike Judge)
讓我們從面向對象的角度分析推理并查看其原因。
這是一個具有一個主要構造函數和兩個次要構造函數的類:
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
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的构造函数还是静态工厂方法?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 递归改非递归_使用Java T
- 下一篇: 使用JDK 8将收藏转换为地图