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