第1條:考慮採用靜態工廠方法代替構造器
第1條:考慮採用靜態工廠方法代替構造器
對類而言,為了讓客戶端獲取它自身的一個例項,最常用的方法就是提供一個公有的構造器。還有一種應該被程式設計師重視的方法:類提供一個公有的靜態工廠方法(static factory method),它只是一個返回類的例項的靜態方法。下面是來自Boolean(基本型別boolean的包裝類)的簡單示例,這個方法將boolean基本型別值轉換成了一個Boolean物件引用:
1 public static Boolean valueOf(boolean b) { 2 return b?Boolean.TRUE:Boolean.FALSE;3 }
類可以通過靜態工廠方法來提供它的客戶端,而不是通過構造器。提供靜態工廠方法而不是公有構造器,這樣做有幾大優勢:
靜態工廠方法與構造器不同的第一大優勢在於,它們有名稱。問題:市場上存在三種類型的可樂,不含糖、不含脂和含脂含糖其餘的引數一樣,請設計一個類用於返回兩種不同型別的可樂。
1 public class Coca { 2 private final int carbohydrate; 3 private final int calories; 4 private final int fat; 5 private finalint sugar; 6 //沒問題,返回含糖含脂的可樂 7 public Coca(int carbohydrate, int calories, int fat, int sugar) { 8 super(); 9 this.carbohydrate = carbohydrate; 10 this.calories = calories; 11 this.fat = fat; 12 this.sugar = sugar; 13 } 14 //沒問題,返回不含糖的可樂例項15 public Coca(int carbohydrate, int calories, int fat) { 16 super(); 17 this.carbohydrate = carbohydrate; 18 this.calories = calories; 19 this.fat = fat; 20 this.sugar = 0; 21 } 22 //預期希望的結果:返回不含脂的可樂例項 23 //編譯錯誤:Duplicate method Coca(int, int, int) in type Coca 24 public Coca(int carbohydrate, int calories, int sugar) { 25 super(); 26 this.carbohydrate = carbohydrate; 27 this.calories = calories; 28 this.sugar = sugar; 29 this.fat = 0; 30 } 31 }
從上面很明顯可以看出,因為構造器區別在於引數型別、個數、順序不同而已,上面的第三個和第二個構造方法並沒有這些不同,因此無法區別才導致報錯。這時候,我們幸好有靜態工廠方法,我們可以通過使用簡單的公共靜態方法返回一個類的例項。
1 public class Coca { 2 private final int carbohydrate; 3 private final int calories; 4 private final int fat; 5 private final int sugar; 6 private Coca(int carbohydrate, int calories, int fat, int sugar) { 7 this.carbohydrate = carbohydrate; 8 this.calories = calories; 9 this.fat = fat; 10 this.sugar = sugar; 11 } 12 public static Coca includeAllCoca(int carbohydrate, int calories, int fat, int sugar) { 13 return new Coca(carbohydrate,calories,fat,sugar); 14 } 15 public static Coca noFat(int carbohydrate, int calories, int sugar) { 16 return new Coca(carbohydrate,calories,0,sugar); 17 } 18 public static Coca noSugar(int carbohydrate, int calories, int fat) { 19 return new Coca(carbohydrate,calories,fat,0); 20 } 21 }
總結:當一個類需要多個帶有相同簽名的構造器時,就用靜態工廠方法代替構造器,並且慎重地選擇名稱以便突出它們之間的區別。
靜態工廠方法與構造器不同的第二大優勢在於,不必每次在呼叫它們的時候都建立一個新的物件。
1 public class Elvis { 2 private static final Elvis INSTANCE = new Elvis(); 3 private Elvis() {}; 4 public static Elvis getInstance() { 5 return INSTANCE; 6 } 7 }
靜態工廠方法能夠為重複的呼叫返回相同的物件,這樣有助於類總能嚴格控制在某個時刻哪幾個例項應該存在,這種類被稱為例項受控的類(instance-controlled)。編寫例項受控的類有幾個原因。
一:例項受控能保證它是一個Singleton或者是不可例項化的。
二:它還使得不可變的類可以確保不會存在兩個相等的例項,即當且僅當a==b的時候才有a.equals(b)為true,如果能保證這一點,客戶端就可以使用==來代替equals(Object)方法,這樣可以提升效能。
靜態工廠方法與構造器不同的第三大優勢在於,它們可以返回原返回型別的任何子型別物件。這樣我們在選擇返回物件的類的時候就有了更大的靈活性。
1 public class Person { 2 public String name; 3 public static Person getInstance() { 4 return new Cooker(); //此處可返回new Cooker()或者new Player() 5 } 6 } 7 8 class Player extends Person{ 9 10 } 11 12 class Cooker extends Person{ 13 private String food; 14 public Cooker setName(String name) { 15 this.name = name; 16 return this; 17 } 18 public Cooker setFood(String food) { 19 this.food = food; 20 return this; 21 } 22 public void cook() { 23 System.out.println(name+"正在烹飪"+food+",請稍後..."); 24 } 25 }
客戶端程式碼:
1 Cooker cooker = (Cooker) Person.getInstance(); 2 cooker.setName("XXX").setFood("紅燒獅子頭").cook();
執行結果:
XXX正在烹飪紅燒獅子頭,請稍後...
靜態工廠方法的第四大優勢在於,在建立引數化型別例項的時候,它們使程式碼變得更加簡潔。遺憾的是,在呼叫引數化類的構造器時,即使型別引數很明顯,也必須指明。這通常要求你接連兩次提供型別引數:
Map<String,List<String>> m = new HashMap<String,List<String>>();
隨著型別引數變得越來越長,越來越複雜,這一冗長的說明也很快變得痛苦起來。但是有了靜態工廠方法,編譯器就可以替你找到型別引數。這被稱作型別推導(type inference)。例如,假設HashMap提供了這個靜態工廠:
1 public static <K,V> HashMap<K,V> newInstance(){ 2 return new HashMap<K,V>(); 3 }
你就可以用下面這句簡潔的程式碼代替上面這段繁瑣的宣告:
Map<String,List<String>> m = HashMap.newInstance();
靜態工廠方法的主要缺點在於,類如果不含公共的或者受保護的構造器,就不能被子類化。例如靜態工廠方法的優勢二中的例子,該類無法被子類化。
靜態工廠方法的第二個缺點在於,它們與其它的靜態方法實際上沒有任何區別。在API文件中,它們沒有像構造器那樣在API文件中明確標識出來,因此,對於提供了靜態工廠方法而不是構造器的類來說,要想查明如何例項化一個類,這是非常困難的。
下面是靜態工廠方法的一些慣用名稱:
valueOf-----不太嚴格地講,該方法返回的例項與它的引數具有相同的值,這樣的靜態工廠方法實際上是型別轉換的方法。
of-----valueOf的一種更為簡潔的替代。
getInstance-----返回的例項是通過方法的引數來描述的,但是不能夠說與引數具有同樣的值。對於Singleton來說,該方法沒有引數,並返回唯一的例項。
newInstance-----像getInstance一樣,但是newInstance能夠確保返回的每個例項都與所有其它的例項不同。
getType-----像getInstance一樣,但是在工廠方法處於不同的類中的時候使用,Type表示工廠方法所返回的物件型別。
newType-----像getInstance一樣,但是在工廠方法處於不同的類中的時候使用,Type表示工廠方法所返回的物件型別。