1. 程式人生 > >1、用靜態工廠方法代替構造器

1、用靜態工廠方法代替構造器

一、客戶端獲取類的一個例項,有兩種解決方案

  1. 最傳統的方法就是提供一個公有的構造器。
  2. 類提供一個公有的靜態工廠方法,返回一個類的例項的靜態方法。

二、靜態工廠方法的優點

1、有方法名,可讀性強

  1. 不同的靜態工廠方法,有自己的專屬名稱。如果構造器的引數本身沒有確切地描述正被返回的物件,那麼具有名稱的靜態工廠方法會更容易使用,產生的客戶端程式碼更易於閱讀。
  2. 如果一昧的建立各種不同方法簽名的構造方法,就會導致構造方法爆炸,而且不利於管理,使用者僅僅依靠引數型別、個數來分辨不同構造方法的作用,直接增加了開發成本,還增加了出錯的機率,程式碼可讀性不強。
  3. 靜態工廠方法有名稱,當一個類需要多個帶有不同簽名的構造器時,我們可以使用靜態工廠方法代替構造器,並且用規則的命名來展示出不同方法對應的作用。只要方法命名規範,使用者完全可以通過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 }
This is Code!

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 }
This is Code!

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 }
This is Code!

 

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 }
DLC靜態工廠方法

 

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;
    }



}
This is Code!

 

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 }
This isCode!

 

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 }
This is Code!

 

 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 }
This is Test!

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 }
View Code

 

參考這篇文章進一步理解: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版) +  自己的理解 + 大佬部落格參