[憨讀記 之 Effective Java] 01-用靜態工廠方法代替構造器

書的第一章是建立和銷燬對象,接下來的幾篇也都是圍繞這個展開。java

本篇對應書中的第一條:用靜態工廠方法代替構造器。數據庫

什麼是靜態工廠方法

先看一個例子,Boolean類中有以下構造器編程

public Boolean(boolean value) {
    this.value = value;
}

同時,還提供了以下的靜態方法,也能夠返回Boolean類實例設計模式

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

這個就是靜態工廠方法:用一個靜態方法來對外提供自身實例。(非官方定義)數組

要注意,這裏的靜態工廠方法跟設計模式中的工廠模式並無什麼對應關係。篇幅緣由(主要是懶,這裏就不詳細說了。微信

靜態工廠方法的優點

優點1:有名稱。框架

構造器的一個缺點是可能無法對建立的對象有明確的描述,例如構造器BigInteger(int, int, Random)返回的BigInteger可能爲素數,可是使用BigInteger probablePrime(int, Random)方法會更加明確。dom

構造器的另外一個缺點是一個類只能有一個帶有指定簽名的構造器,若是想避開這個限制,能夠提供兩個構造器,它們的參數列表只是參數類型的順序不同,但這個會對使用方形成迷惑。靜態工廠方法則不受這個限制。ide

優點2:沒必要在每次調用它們的時候都建立一個新對象。性能

好比上面的Boolean.valueOf方法,返回值已經預先構建好了,不會新建立對象。同時,日常常用的單例模式,通常也都是經過靜態工廠方法實現的。這個比起每次建立一個新對象的成本要低不少。

優點3:能夠返回原返回類型的任何子類型的對象。

提供了極大的靈活性,對於面向接口編程很是適用。好比平常的工做中,能夠定義方法的返回值爲一個接口,在實際返回的時候多是該接口的任意實現類。

另外,還能夠返回對象,同時又不會使對象的類變成公有的。

java.util.Collections中有不少這種使用方式:

public static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
    return new SetFromMap<>(map);
}

而這個SetFromMap就是一個私有的類

private static class SetFromMap<E> extends AbstractSet<E> implements Set<E>, Serializable{
    ...
}

優點4:所返回的對象的類能夠隨着每次調用而發生變化,這取決於靜態工廠方法的參數值。

這個看文本大概就明白什麼意思,跟上一點有點類似,不過更強調的是經過參數值返回不一樣的類,有多是基於性能的考慮,也有多是基於業務的考慮。好比根據數組的大小返回不一樣的實現。

優點5:在編寫包含該方法的類時,返回的對象的類不須要存在。

這是服務提供者框架的基礎,典型的應用場景就是JDBC,具體的實現類都是各個數據庫驅動包中提供的,編寫jdbc相關代碼時並不存在。

可能你第一次據說服務提供者框架這個名詞,可是你應該早就接觸過了。

服務提供者框架是指:多個服務提供者實現一個服務,系統爲客戶端提供多個實現,並把他們從多個實現中解耦出來。

服務提供者的改變對它們的客戶端是透明的,這樣提供了更好的可擴展性。例如,JDBC,JMS等就是用了服務提供者框架。

有四個組件

  • Service Interface:服務接口,將服務經過抽象統一聲明,供客戶端調用、由各個服務提供者具體實現。
  • Provider Registration API:服務提供者註冊API,用於系統註冊服務提供者,使得客戶端能夠訪問它實現的服務。
  • Service Access API:服務訪問API,用戶客戶端獲取相應的服務。
  • Service Provider Interface:服務提供者接口,這些服務提供者負責建立其服務實現的實例。(可選)

看不懂彆着急,能夠對應到JDBC

  • Service Interface:Connection,客戶端的調用都是基於Connection
  • Provider Registration API:DriverManager.registerDriver,註冊服務提供者的API,數據庫驅動會調用這個API把本身註冊。
  • Service Access API:DriverManager.getConnection獲取服務的API。
  • Service Provider Interface:Driver,用於建立Connection

這個模式很好用,最近的代碼中一直在用這個模式,有興趣的能夠去看看JDBC源碼。

靜態工廠方法的劣勢

劣勢1:沒有公共或受保護構造方法的類不能被子類化

這個其實還好,若是是本身寫的類,有須要的話能夠在提供靜態工廠方法的同時提供公有的或者受保護的構造器。若是還不行,可使用組合而不是繼承,應該知道是啥意思哈,不懂後邊也會寫(不是這一篇,估計是10篇之後了)。

劣勢2:很難找到它們

別笑,這個真實地發生了。我寫了一個服務,參數是一個ValueFilter類型的對象,這個類是我本身定義的,而後同事就跟我說你建立這個類須要好多參數,建立起來很麻煩,其實我早就貼心地建好了靜態工廠方法,方便別人使用。可是別人在用你的類的時候不必定會看你的方法,建立對象的時候下意識的都是想着構造器。

下面是一些靜態工廠方法的慣用名稱(照抄

  • from —— 類型轉換方法,它只有單個參數,返回該類型的一個相對應的實例,例如:Date d = Date.from(instant);
  • of —— 聚合方法,帶有多個參數,返回該類型的一個實例,把它們合併起來,例如:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf —— 比from 和 to 更繁瑣的一種替代方式,例如: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 —— 像 newInstance 同樣,可是在工廠方法處於不一樣的類中的時候使用。Type表示工廠方法返回的對象類型,例如:BufferedReader br = Files.newBufferedReader(path);
  • type —— getType 和 newType 的簡版例如:List litany = Collections.list(legacyLitany);

總結

在須要提供實例的時候不要第一反應就提供公有的構造器,能夠優先考慮靜態工廠。

參考

看到了這裏必定是真愛了,關注微信公衆號【憨憨的春天】第一時間獲取更新
qrcode_for_gh_7fff61e23381_344.jpg

相關文章
相關標籤/搜索