Effective Java 讀書筆記——1:考慮用靜態工廠方法代替構造器

類可以提供一個公有的靜態工廠方法(static factory method),是一個用來返回類的實例靜態方法。比如,下面可以通過boolean來返回Boolean對象引用的方法:

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

上面是一個非常簡單的靜態工廠方法,我們進一步可以看到Boolean.TRUE也是非常直觀簡單的:

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

注意,這裏所說的靜態工廠方法和設計模式中的工廠方法模式存在一點區別。下面進一步說明的是,類可以通過靜態工廠方法來提供它的實現,而不是構造器。

優勢

  1. 靜態工廠方法有名稱。儘管構造器可以通過傳入參數的不同,進行重載。但是,往往有限的參數無法正確描述需要返回的對象,這時候,具有適當名稱的靜態工廠更容易使用,代碼也更容易閱讀。
  2. 不必每次調用的時候都創建一個新對象。這樣可以使不可變類重複使用相同的實例,比如上面這個例子。
  3. 可以返回原返回類型任何子類型的對象。這種靈活性的一種應用是:API可以返回對象,同時又不會使這種對象的類變成公有的。以這種方式隱藏實現類會使API變的簡潔。如,Java Collection集合類。
比如在java.util.EnumSet中就沒有公有構造器,只有靜態工廠方法。它返回兩種實現類之一,根據底層枚舉類型的大小,如:
    /**
     * Creates an empty enum set with the specified element type.
     *
     * @param <E> The class of the elements in the set
     * @param elementType the class object of the element type for this enum
     *     set
     * @return An empty enum set of the specified type.
     * @throws NullPointerException if <tt>elementType</tt> is null
     */
    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum<?>[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }

然而這兩種實現類,對於客戶端來說是不可見的。未來的版本中,可能會刪除沒有用的實現類,或者是加上更多的實現類,這對API是沒有任何影響的。

服務提供框架

靜態工廠方法返回的對象所屬的類,在編寫包含該靜態工廠方法的類的時候可以不必存在。這種靈活的靜態工廠方法構成了服務提供者框架(Service Provider Framework)的基礎。如,JDBC API。服務提供框架是指這樣一個系統:多個服務提供者實現一個服務,系統爲服務提供者的客戶端提供多個實現,並把他們從多個實現中解耦出來。
服務提供者框架中,有三個重要組成組件:
  1. 服務接口(Service Interface)。提供者實現的。
  2. 提供者註冊API(Provider Registeration API)。系統用來註冊實現的,讓客戶端來訪問。
  3. 服務訪問API(Service Access API)。客戶端用來獲取服務實例。服務訪問API一般允許但是不要求客戶端指定某種選擇提供者的條件,如果沒有這樣的規定,API就會範圍默認的一個實現實例。服務訪問API是“靈活的靜態工廠”,它構成該框架的基礎。
下面就是一個簡單實現,包含一個服務提供者接口和默認提供者:
public interface Service {
    // Service-specific methods go here
}

public interface Provider {
    Service newService();
}

public class Services {
    private Services() { }  // Prevents instantiation (Item 4)

    // Maps service names to services
    private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
    public static final String DEFAULT_PROVIDER_NAME = "<def>";

    // Provider registration API
    public static void registerDefaultProvider(Provider p) {
        registerProvider(DEFAULT_PROVIDER_NAME, p);
    }
    public static void registerProvider(String name, Provider p){
        providers.put(name, p);
    }

    // Service access API
    public static Service newInstance() {
        return newInstance(DEFAULT_PROVIDER_NAME);
    }
    public static Service newInstance(String name) {
        Provider p = providers.get(name);
        if (p == null)
            throw new IllegalArgumentException(
                "No provider registered with name: " + name);
        return p.newService();
    }
}


缺點

  1. 類如果不含有公有的或者受保護的構造器,就不能被子類化。這是顯而易見的,一般靜態工廠方法返回對象,還是要通過一個構造器(常常是受保護)來進行,無論是自身所在的類,還是返回其他的類。
  2. 靜態工廠方法和其他靜態方法是一樣的,很難明確標示出來。對於通過靜態工廠方法來實例化一個類的類來說,想查明如何實例化這個類是比較困難的。因此,常常會通過一些慣用名稱來彌補這一劣勢,如:valueOf,of,getInstance,newInstance,getType,newType.
靜態工廠方法和公有構造器同樣重要,切忌第一反應是提供公有構造器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章