當EffectiveJava遇見Guava - 靜態工廠方法代替構造器(規則1)
Effective Java中指出,使用靜態工廠方法代替構造器有幾大優勢:
第一大優勢 - 他們有名稱。
多個構造器只能通過匹配參數類型的順序不同來區分使用哪一個,這樣常常會導致用戶調用錯誤構造器,而靜態工程方法則不同,可以通過方法名清晰的指明用意。
//本例只用來說明第一大優勢,請不要糾結其它問題
public class Foo {
Set<Bar> bars;
List<Car> cars;
//構造器1
private Foo(Set<Bar> bars) {
this.bars = bars;
}
//構造器2
private Foo(List<Car> cars) {
this.cars = cars;
}
//構造器3
private Foo(Set<Bar> bars, List<Car> cars) {
this.bars = bars;
this.cars = cars;
}
//靜態工廠方法1
public static Foo newInstanceByBar(){
return new Foo(new HashSet<Bar>());
}
//靜態工廠方法2
public static Foo newInstanceByCar(){
return new Foo(new ArrayList<Car>());
}
//靜態工廠方法3
public static Foo newInstanceByAll(){
return new Foo(new HashSet<Bar>(),new ArrayList<Car>());
}
public static void main(String[] args) {
// 通過構造器創建實例,不好區分容易使用錯誤
Foo fbar = new Foo(new HashSet<Bar>());
Foo fcar = new Foo(new ArrayList<Car>());
Foo fall = new Foo(new HashSet<Bar>(),new ArrayList<Car>());
// 通過靜態工廠方法可以清晰的用方法名識別
Foo fbar_static = Foo.newInstanceByBar();
Foo fcar_static = Foo.newInstanceByCar();
Foo fall_static = Foo.newInstanceByAll();
}
}
class Bar {}
class Car {}
對于Guava,并沒有提供創建靜態工廠方法的工具,但整個Guava API到處都是靜態方法的實現,我們以Guava Collections Framewrok舉例說明。
Guava對于第一大優勢有很多實現:
List<Type> exactly100 = Lists.newArrayListWithCapacity(100);
List<Type> approx100 = Lists.newArrayListWithExpectedSize(100);
Set<Type> approx100Set = Sets.newHashSetWithExpectedSize(100);
第二大優勢 - 不必在每次調用他們的時候都創建一個新對象。
方便對象重用,還可以確保不可變的不會存在兩個相等的實例,如果a==b那么a.equals.(b)才會返回true ,如果能保證這一點,就可以使用==操作符來比較對象,會有很大的性能提升。
第三大優勢 - 他們可以返回原返回類型的任何子類型的對象。
這是一個非常強大的特性, Effective Java中列舉了API、SPI、服務提供框架的關系來說明:
API(Service Interface): 服務公共接口 SPI(Service Provider Interface): 服務提供商接口 SPF(Service Provider Framework): 服務提供框架
看例子:
// 服務提供框架示意模型 - 服務API
public interface ServiceAPI {
// 這里是服務指定的方法
}
// 服務提供框架示意模型 - 服務SPI
public interface ServiceSPI {
ServiceAPI newService();
}
// 服務提供框架示意模型實現
// 不可實例化的類,用來注冊創建和提供訪問
public class ServiceFramework {
private ServiceFramework() {
}// 強制防止實例化(規則4)
// 映射服務名到服務
private static final ConcurrentMap<String, ServiceSPI> spis = new MapMaker().makeMap();//使用Guava創建
public static final String DEFAULT_SPI_NAME = "<def>";
// 默認SPI注冊API
public static void registerDefaultSPI(ServiceSPI spi) {
registerSPI(DEFAULT_SPI_NAME, spi);
}
// 指定SPI注冊API
public static void registerSPI(String name, ServiceSPI spi) {
spis.put(name, spi);
}
// 服務訪問API
public static ServiceAPI newInstance() {
return newInstance(DEFAULT_SPI_NAME);
}
public static ServiceAPI newInstance(String name) {
ServiceSPI spi = spis.get(name);
if(spi == null)
throw new IllegalArgumentException(
"No provider registered with name: " + name);
return spi.newService();
}
}
Note | 靜態工程方法返回的對象所屬的類,在編寫這個包含靜態工廠方法的類時可以不必存在。上面的例子在編寫ServiceFramework類時,ServiceAPI的實現類并不存在。這大大增加了框架的靈活性。 |
現在編寫客戶端測試程序
// 簡單的服務提供框架測試程序
public class Test {
public static void main(String[] args) {
// 服務提供商執行下面的注冊
ServiceFramework.registerDefaultSPI(DEFAULT_PROVIDER);
ServiceFramework.registerSPI("comp", COMP_PROVIDER);
ServiceFramework.registerSPI("armed", ARMED_PROVIDER);
// 客戶端執行下面的創建
ServiceAPI s1 = ServiceFramework.newInstance();
ServiceAPI s2 = ServiceFramework.newInstance("comp");
ServiceAPI s3 = ServiceFramework.newInstance("armed");
System.out.printf("%s, %s, %s%n", s1, s2, s3);
}
private static ServiceSPI DEFAULT_PROVIDER = new ServiceSPI() {
public ServiceAPI newService() {
return new ServiceAPI() {
@Override
public String toString() {
return "默認服務";
}
};
}
};
private static ServiceSPI COMP_PROVIDER = new ServiceSPI() {
public ServiceAPI newService() {
return new ServiceAPI() {
@Override
public String toString() {
return "Complementary 服務";
}
};
}
};
private static ServiceSPI ARMED_PROVIDER = new ServiceSPI() {
public ServiceAPI newService() {
return new ServiceAPI() {
@Override
public String toString() {
return "Armed 服務";
}
};
}
};
}
//輸出如下 默認服務, Complementary 服務, Armed 服務
第四大優勢 - 在創建參數化類型實例的時候,他們使代碼變得更加簡潔。
在JDK7之前,我們創建一個Collections大致是這么做的:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();
JDK7發布以后,我們可以簡化成這樣:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();
但是Guava還是寧愿使用靜態工程方法,因為真的非常方便:
Set<Type> copySet = Sets.newHashSet(elements);
List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");
靜態工程方法的缺點
類如果不含公有的或者受保護的構造器,就不能被子類化,這也許會因禍得福,因為它鼓勵開發人員使用復合,而不是繼承。
他們與其他的靜態方法實際上沒有任何區別 如果API文檔沒有明確的說明這是一個靜態工程方法,就會很難識別出來。遵循標準的命名規范習慣,可以彌補這一劣勢,下面列出一些慣用命名:
valueOf - 這樣的靜態工廠方法實際上是類型轉換
of - valueOf的簡潔方式
getInstance - 返回實例通過方法參數描述,對于單例,該方法沒有參數,并返回唯一的實例
newInstance - 與getInstance不同的是,它返回的實例與所有其它實例都是不同的
getType - 像getInstance一樣,但是在工廠方法處于不同的類中的時候使用。Type表示i返回對象類型
newType - 像newInstance一樣,但是在工廠方法處于不同的類中的時候使用。Type表示i返回對象類型
posted on 2013-05-30 17:09 kuuyee 閱讀(3885) 評論(1) 編輯 收藏 所屬分類: JEE