使用靜態工廠方法而不是構造器

注意:靜態工廠方法不是設計模式中的工廠方法。java

一個類向客戶端提供靜態工廠方法有以下好處:程序員

  • 有名稱,不用根據參數類型和順序區分重載方法,讓代碼更易讀
  • 是否每次調用都須要新對象是可控制的,對於不可修改的對象能夠採起緩存對象來提升性能,例如可使用==來判斷對象是否相等,而不使用equals,能夠提升性能。
  • 工廠方法體內可返回返回類型的任何子類型,這在選擇返回對象的類型上有很大的靈活性。一個對外隱蔽了實現類(即所返回的對象的類型對外是不可見的)的API是很是緊湊的API。這種技巧自己把本身引導成了基於接口的框架(接口爲靜態工廠方法提供返回類型)。接口不能有靜態方法,所以按慣例返回類型爲接口Type的靜態工廠方法會放在一個不可實例化的類Types中:例如Java集合框架中的Collections有32個對集合接口的很是方便的實現,分別提供不可修改集合、同步集合等等,幾乎全部這些實現都是經過一個不可實例化的類(java.util.Collections)的靜態工廠方法導出,這些工廠所返回的類都是非public的。當前集合框架API比導出32個public類要更小,不單隻在API的代碼量上獲得減小,同時還在概念的重量級方面。用戶一眼就知道所返回的對象正如接口所描述的同樣,不須要讀取額外的類實現文檔。另外,使用這樣一種靜態工廠方法須要客戶端使用接口來引用這個工廠方法所返回的對象,而不是使用實現類引用,這是一個好的實踐。不僅由靜態工廠返回的對象的類型能夠是非public的,根據傳入靜態工廠的參數的不一樣,這個返回對象的類也是可變的,只要是靜態工廠方法聲明的返回值類型的子類型就都是容許的。靜態工廠返回值類型也是能夠在各發布版間變化的,以加強可維護性和性能。Java1.5引入的java.util.EnumSet沒有public構造器,只有靜態工廠,這些靜態工廠根據底層枚舉類型的大小返回兩個實現中的一個:若是元素小於等於64,靜態工廠返回一個RegularEnumSet實例,它由單個long支持; 若是枚舉類型有大於64個元素,靜態工廠會返回一個JumboEnumSet實例,它由一個long數組支持。這兩個實現類對客戶端是不可見的。

     由靜態工廠方法返回的對象的類甚至沒必要在編寫靜態工廠方法所在的類時就存在。靜態工廠的這種靈活性是服務提供者框架(service provider framework)的基石,例如JDBC。服務提供者框架是一個系統,在這個系統中,服務提供者實現服務,這個系統讓這些實現對客戶端可用,從而把客戶端與實現分離。設計模式

     服務提供者框架有三個基本的組件:由提供者實現的服務接口、系統用於註冊服務實現並讓其對客戶端可用的提供者註冊API、客戶端用於獲取服務實現的服務訪問API。服務訪問API一般容許讓客戶端指定某些條件以便選擇一個提供者,但客戶端不是必需要指定,若是沒有指定,API會返回一個默認實現。服務訪問API就是構成服務提供者框架的基石的靈活靜態工廠。數組

    服務提供者接口有一個可選的組件:服務提供者接口,由提供者用於建立他們本身的服務實現的實例。在沒有服務提供者接口的狀況下,實現是經過類名進行註冊,並經過反射進行實例化。在JDBC中,Connection就是服務接口,DriverManager.registerDriver()就是服務提供者註冊接口,DriverManager.getConnection()是服務訪問接口,Driver是服務提供者接口。緩存

    如今有許多服務提供者框架模式的變種,例如,經過使用適配器,服務訪問API能夠返回比提供者所須要的服務接口更加豐富的服務接口,下面是一個服務提供者接口及其一個默認實現:框架

// Service provider framework sketch
// Service interface
public interface Service {
    ... // Service-specific methods go here
}
// Service provider interface
public interface Provider {
    Service newService();
}
// Noninstantiable class for service registration and access
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();
    }
} ide

  • 減小建立參數化類型實現時的冗餘信息

沒有使用靜態工廠時:工具

Map<String, List<String>> m = new HashMap<String, List<String>>(); 性能

使用靜態工廠後: 設計

public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
}

Map<String, List<String>> m = HashMap.newInstance();

遺憾的是,標準的集合類實現中,例如HashMap並無相似上面定義的工廠方法。但能夠把這種方法放到本身的工具類中,更加劇要的是,能夠在本身的參數化類中提供這種靜態工廠方法。

 

只提供靜態工廠方法的類的主要缺點在於不能被子類化,由於沒有public或protected的構造器的類是不能被子類化的。

由public權限的靜態工廠返回的非public類也是不能被子類化的,例如,不能子類化Collections裏面的實現類,這能夠說是塞翁失馬,所以這樣能夠促進程序員使用組合而不是繼承。

另外一個缺點在於靜態工廠方法不易與其它靜態方法區分開。

主要是由於靜態工廠方法不像構造器那樣明顯地出如今API文檔中,所以很難知道如何使用類中提供的靜態工廠來代替構造方法實例化對象。能夠經過在類或接口中寫註釋來講明,而且讓靜態工廠方法名遵循約定:

valueOf、of、getInstance、newInstance、getType、newType

總之,靜態工廠和構造方法各有優缺點,但一貫在使用構造器以前優先考慮使用靜態工廠。

相關文章
相關標籤/搜索