一、客戶端獲取類的一個實例,有兩種解決方案
- 最傳統的方法就是提供一個公有的構造器。
- 類提供一個公有的靜態工廠方法,返回一個類的實例的靜態方法。
二、靜態工廠方法的優點
1、有方法名,可讀性強
- 不同的靜態工廠方法,有自己的專屬名稱。如果構造器的參數本身沒有確切地描述正被返回的對象,那么具有名稱的靜態工廠方法會更容易使用,產生的客戶端代碼更易於閱讀。
- 如果一昧的創建各種不同方法簽名的構造方法,就會導致構造方法爆炸,而且不利於管理,用戶僅僅依靠參數類型、個數來分辨不同構造方法的作用,直接增加了開發成本,還增加了出錯的幾率,代碼可讀性不強。
- 靜態工廠方法有名稱,當一個類需要多個帶有不同簽名的構造器時,我們可以使用靜態工廠方法代替構造器,並且用規則的命名來展示出不同方法對應的作用。只要方法命名規范,用戶完全可以通過IDE來補全所需要的方法即可,而不需要從頭到尾的看源碼,或者是仔細的閱讀文檔。
2、案例:
很簡單,拿生成訂單來說。如果生成訂單這個作為接口,那么會被多方機構調用。如:A機構所需參數為count、money。B機構所需參數為count、money、address。C機構僅僅是需要money。等等機構的不同需求參數。
a)、使用構造器:

1 package com.turtle.demo; 2 3 public class GenerateOrder { 4 5 private int count; 6 7 /** 8 * 備注:關於錢,切記不要使用double類型,這是例子,就暫時使用double了 9 */ 10 private double money; 11 12 private String address; 13 14 /** 15 * 需要一個參數的構造器 16 * @param money:金額 17 */ 18 public GenerateOrder(double money){ 19 this(0,money,null); 20 } 21 22 /** 23 * 需要兩個參數的構造器 24 * @param count:數量 25 * @param money:金額 26 */ 27 public GenerateOrder(int count, double money){ 28 this(count,money,null); 29 } 30 31 /** 32 * 需要三個參數的構造器 33 * @param count:數量 34 * @param money:金額 35 * @param address:地址 36 */ 37 public GenerateOrder(int count, double money, String address) { 38 this.count = count; 39 this.money = money; 40 this.address = address; 41 } 42 }
b)、使用靜態工廠方法(不是最終形態)

1 package com.turtle.demo; 2 3 public class GenerateOrder { 4 5 private int count; 6 7 /** 8 * 備注:關於錢,切記不要使用double類型,這是例子,就暫時使用double了 9 */ 10 private double money; 11 12 private String address; 13 14 /** 15 * 需要三個參數的私有化構造器 16 * @param count:數量 17 * @param money:金額 18 * @param address:地址 19 */ 20 private GenerateOrder(int count, double money, String address) { 21 this.count = count; 22 this.money = money; 23 this.address = address; 24 } 25 26 /** 27 * A機構所需參數為count、money。 28 * @param count 29 * @param money 30 * @return 31 */ 32 public static GenerateOrder generateOrderByA(int count,double money){ 33 return new GenerateOrder(count,money,null); 34 } 35 36 /** 37 * B機構所需參數為count、money、address。 38 * @param count 39 * @param money 40 * @param address 41 * @return 42 */ 43 public static GenerateOrder generateOrderByB(int count,double money,String address){ 44 return new GenerateOrder(count,money,address); 45 } 46 47 /** 48 * C機構僅僅是需要money 49 * @param money 50 * @return 51 */ 52 public static GenerateOrder generateOrderByC(double money){ 53 return new GenerateOrder(0,money,null); 54 } 55 }
c)、調用:

1 package com.turtle.test; 2 3 import com.turtle.demo.GenerateOrder; 4 5 /** 6 * 對生成訂單做測試 7 */ 8 public class TestGenerateOrder { 9 10 public static void main(String[] args) { 11 12 // 使用構造器 13 // A 機構創建訂單 14 GenerateOrder generateOrder_A = new GenerateOrder(1,200D); 15 16 // B 機構創建訂單 17 GenerateOrder generateOrder_B = new GenerateOrder(1,200D,"深圳"); 18 19 // C 機構創建訂單 20 GenerateOrder generateOrder_C = new GenerateOrder(200D); 21 22 23 // 使用靜態工廠方法 24 // A 機構創建訂單 25 GenerateOrder generateOrder_AA = GenerateOrder.generateOrderByA(1,200D); 26 27 // B 機構創建訂單 28 GenerateOrder generateOrder_BB = GenerateOrder.generateOrderByB(1,200D,"深圳"); 29 30 // C 機構創建訂單 31 GenerateOrder generateOrder_CC = GenerateOrder.generateOrderByC(200D); 32 } 33 }
2、不必在每次調用它們的時候都創建一個新對象。
可以使不可變類直接使用預先構建好的實例,或者將構建好的實例緩存起來,進行重復利用,如果程序經常請求創建相同的對象,並且創建對象的代價很高,則這項技術可以極大地提升性能。
靜態工廠方法能夠為重復的調用返回相同對象,這樣有助於類總能嚴格控制在某個時刻哪些實例應該存在,可以直接和單例搭配起來使用。
單例的幾種寫法:

1 package com.turtle.singleton; 2 3 public class SingletonDemo { 4 5 /** 6 * 構造函數私有化 7 */ 8 private SingletonDemo(){ 9 } 10 11 // 維護一個單例對象 12 private static SingletonDemo singletonDemo = new SingletonDemo(); 13 14 public static SingletonDemo getInstance(){ 15 return singletonDemo; 16 } 17 }

1 package com.turtle.singleton; 2 3 public class SingletonDemo { 4 5 /** 6 * 構造函數私有化 7 */ 8 private SingletonDemo(){ 9 } 10 11 // 維護一個單例對象 12 private static SingletonDemo singletonDemo; 13 14 public static SingletonDemo getInstance(){ 15 if(singletonDemo == null){ 16 singletonDemo = new SingletonDemo(); 17 } 18 return singletonDemo; 19 } 20 }

1 package com.turtle.singleton; 2 3 public class SingletonDemo { 4 5 /** 6 * 構造函數私有化 7 */ 8 private SingletonDemo(){ 9 } 10 11 // 維護一個單例對象 12 // volatile 是1.5后優化JAVA內存模型的關鍵字 13 private volatile static SingletonDemo singletonDemo; 14 15 public static SingletonDemo getInstance(){ 16 if(singletonDemo == null){ 17 // 由於內存模型,靜態工廠方法多線程情況下也會有問題,即使用了雙重鎖定也一樣 18 synchronized (SingletonDemo.class){ 19 if(singletonDemo == null){ 20 singletonDemo = new SingletonDemo(); 21 } 22 } 23 } 24 return singletonDemo; 25 } 26 }
3、它們可以返回原返回類型的任何子類型的對象
構造器只能返回當前類的實例,無法返回子類的實例。雖說推薦的是復合,而不推薦繼承。但是有時還是會因為業務需求,可能需要返回對應的子類型對象。這個時候依靠構造器就無法完成了,只能單獨創建對應的返回子對象的方法來實現對應需求。但是使用靜態方法我們就可以很好的解決這個問題。還減少了重復的方法創建。

package com.turtle.demo; public class Animal { protected Animal(){ } private static Animal dog; private static Animal snake; /** * 返回子類型 Dog * @return */ public static Animal getAnimal_Dog(){ if(dog == null){ dog = new Dog(); } return dog; } /** * 返回子類型 Snake * @return */ public static Animal getAnimal_Snake(){ if(snake == null){ snake = new Snake(); } return snake; } }
4、返回的類可以隨着每次調用而動態變化,這取決於靜態工廠的方法的參數值
靜態工廠的第四大優勢在於,所返回的對象的類可以隨着每次調用而發生變化,這取決於靜態工廠方法的參數值。只要是已聲明的返回類型的子類型,都是允許的。返回對象的類也可能隨着發行版本的不同而不同。
EnumSet 沒有公有的構造器,只有靜態工廠方法。在OpenJdk實現中,它們返回兩種子類之一的一個實例,具體則取決於底層枚舉類型的大小:如果它的元素有6 4個或者更少,就像大多數枚舉類型一樣,靜態工廠方法就會返回一個RegularEnumSet實例,用單個long進行支持;
如果枚舉類型有65個或者更多元素,工廠就返回JumboEnumSet實例,用一個long數組進行支持。

1 public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { 2 Enum<?>[] universe = getUniverse(elementType); 3 if (universe == null) 4 throw new ClassCastException(elementType + " not an enum"); 5 6 if (universe.length <= 64) 7 return new RegularEnumSet<>(elementType, universe); 8 else 9 return new JumboEnumSet<>(elementType, universe); 10 }
5、代碼更加簡潔,減少我們的代碼量。

1 package com.turtle.demo; 2 3 class MyMap<K,V> { 4 5 public MyMap() { 6 } 7 8 public static <K,V> MyMap<K,V> getInstance(){ 9 return new MyMap<K, V>(); 10 } 11 }

1 package com.turtle.demo; 2 3 public class Main { 4 public static void main(String[] args) { 5 // 實例時需要制定泛型 6 MyMap<String, String> map1 = new MyMap<String, String>(); 7 8 //更加簡潔,不需要重復指明類型參數,可以自行推導出來 9 MyMap<String, String> map2 = MyMap.getInstance(); 10 } 11 }
6、靜態工廠返回的類可以不存在
靜態工廠的第五大優勢在於,方法返回對象所屬的類,在編寫包含該靜態工廠方法類時可以不存在。
(靜態工廠方法最典型的實現--服務提供者框架 )服務提供者框架包含四大組件:
- 服務接口:這是服務提供者要去實現的接口
- 服務提供者接口:生成服務接口實例的工廠對象(就是用來生成服務接口的)(可選)
- 提供者注冊API:服務者 提供服務者自身的實現
- 服務訪問API:根據客戶端指定的某種條件去實現對應的服務提供者

1 //四大組成之一:服務接口 2 public interface LoginService {//這是一個登錄服務 3 public void login(); 4 } 5 6 //四大組成之二:服務提供者接口 7 public interface Provider {//登錄服務的提供者。通俗點說就是:通過這個newLoginService()可以獲得一個服務。 8 public LoginService newLoginService(); 9 } 10 11 /** 12 * 這是一個服務管理器,里面包含了四大組成中的三和四 13 * 解釋:通過注冊將 服務提供者 加入map,然后通過一個靜態工廠方法 getService(String name) 返回不同的服務。 14 */ 15 public class ServiceManager { 16 private static final Map<String, Provider> providers = new HashMap<String, Provider>();//map,保存了注冊的服務 17 18 private ServiceManager() { 19 } 20 21 //四大組成之三:提供者注冊API (其實很簡單,就是注冊一下服務提供者) 22 public static void registerProvider(String name, Provider provider) { 23 providers.put(name, provider); 24 } 25 26 //四大組成之四:服務訪問API (客戶端只需要傳遞一個name參數,系統會去匹配服務提供者,然后提供服務) (靜態工廠方法) 27 public static LoginService getService(String name) { 28 Provider provider = providers.get(name); 29 if (provider == null) { 30 throw new IllegalArgumentException("No provider registered with name=" + name); 31 32 } 33 return provider.newLoginService(); 34 } 35 }
參考這篇文章進一步理解:JAVA 服務提供者框架介紹
三、靜態工廠方法的缺點
1、靜態工廠方法依賴於構造函數的創建
上面提到了一些靜態工廠方法的優點,那么任何事情都有利弊,靜態工廠方法主要缺點在於,類如果不含公有的或者受保護的構造器,就不能被子類化。例如,要想將Collections Framework中任何便利的實現類子類化,這是不可能的。
靜態工廠方法最終也是調用該類的構造方法,如果沒有該類的構造方法,靜態工廠的方法也就沒有意義,也就是說,靜態工廠方法其實是對構造方法的一層封裝,最終還是調用的類的構造方法。
2、靜態工廠方法很難被發現
1、其實主要還是由於習慣,創建對象就直接new就好了,沒有去考慮其他的方法會不會更有優勢。
2、在API文檔中,它們沒有像構造器那樣在API文檔中被標明,因此,對於提供了靜態工廠方法而不是構造器的類來說,要想查明如何實例化一個類是非常困難的。下面提供了一些靜態工廠方法的慣用名稱。這里只列出來了其中的一小部分
from ——— 類型轉換方法,它只有單個參數,返回該類型的一個相對應的實例,例如: Date d = Date.form(instant); of ——— 聚合方法,帶有多個參數,返回該類型的一個實例,把他們結合起來,例如: Set<Rank> faceCards = EnumSet.of(JACK,QUEEN,KING); valueOf ——— 比from 和 of 更繁瑣的一種替代方法,例如: BigInteger prime = BigInteger.valueof(Integer.MAX_VALUE); instance 或者 getInstance ———返回的實例是通過方法的(如有)參數來描述的,但是不能說與參數具有相同的值,例如: StackWalker luke = StackWalker.getInstance(options); create 或者 newInstance ——— 像instance 或者 getInstance 一樣,但create 或者 newInstance 能夠確保每次調用都返回一個新的實例,例如: Object newArray = Array.newInstance(classObject,arrayLen); getType ——— 像getInstance 一樣,但是在工廠方法處於不同的類中的時候使用。Type 表示工廠方法所返回的對象類型,例如: FileStore fs = Files.getFileStore(path); newType ——— 像newInstanfe 一樣,但是在工廠方法處於不用的類中的時候使用,Type表示工廠方法返回的對象類型,例如: BufferedReader br = Files.newBufferedReader(path); type ——— getType 和 newType 的簡版,例如: List<Complaint> litany = Collections.list(legacyLitancy);
四、總結
簡而言之,靜態工廠方法和公有構造器都各有用處,我們需要理解它們各自的長處。靜態工廠經常更加合適,因此切忌第一反應就是提供公有的構造器,而不先考慮靜態工廠。
五、說明
這部分東西比較靈活,加上自己能力有限。有些地方可能會存在理解偏差,這里希望大佬們可以抽出你們寶貴的時間對文章中的錯誤點指正出來,我會及時做調整。謝謝!
【內容源自《Effective Java》(中文原書第3版) + 自己的理解 + 大佬博客參考】