1. 程式人生 > >第1條:考慮採用靜態工廠方法代替構造器

第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 final
int 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表示工廠方法所返回的物件型別。