用靜態工廠方法代替構造器

參考於《Effective Java》

前言

對於類而言,獲得其實例最常見的方式就是提供一個構造器。
如果我們不寫構造器,編譯器會幫我們自動加上一個被public修飾的空構造器。

除了提供構造器以外,靜態工廠方法也應該被考慮到程序的設計當中。

靜態工廠方法

本質上就是類的一個靜態方法,返回值是類的實例對象。

通過私有化構造器,無法直接new對象,而是通過運行靜態工廠方法獲取對象實例。

這麼做既有優勢,也有劣勢。應該結合實際情況來使用。

優勢

1、靜態工廠方法有名稱

我們知道,類的構造器名稱必須和類名相同。

如果類比較複雜,需要多個構造器時,往往只能在構造器的方法簽名上做文章。

通過不同的參數個數,參數類型,參數順序來編寫多個構造器不是個好主意。

面對這樣的API,調用者往往不知所云,到底該調用哪一個構造器。

靜態工廠方法可以很好的規避這一點,通過不同的方法名來區分,調用者就會一目瞭然。

例子

@Data
public class Person {
	private static enum Sex{
		MAN,WOMAN
	}
	private String name;
	private Sex sex;

	private Person(){}

	public static Person createMan(){
		Person person = new Person();
		person.sex = Sex.MAN;
		return person;
	}

	public static Person createWoman(){
		Person person = new Person();
		person.sex = Sex.MAN;
		return person;
	}
}

2、不必在每次調用時都創建新對象

使用構造器創建對象,每次都會返回一個新的對象。
靜態工廠方法使得對象的創建是可控的。

可以將構建好的實例緩存起來進行重複利用,從而避免創建不必要的重複對象。
如果創建對象的代價很高,可以利用這項技術極大地提升性能。

Boolean.valueOf()很好的說明了這點。
它從不創建對象,返回的永遠是自身的TRUE,FALSE常量。

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

還有Integer.valueOf()採用了緩存的機制。
如果數值在 IntegerCache.low 和 IntegerCache.high 之間將從緩存中返回。

public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
		return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}

3、可以返回原返回類型的任意子類

構造器返回的只能是本類的對象實例。

但是靜態工廠方法可以返回原返回類型的任意子類,這讓我們選擇返回類實例時大大的提高了靈活性

當原先的類不能滿足需求,需要替換類時,只需要修改靜態工廠方法,而這一切對於調用者是零感知的。

public class Parent {
	
	public void func(){
		System.out.println("Parent Function.");
	}

	public static Parent getInstance(){
		//返回任意子類實例
		return new Child();
	}
}

public class Child extends Parent {

	@Override
	public void func() {
		super.func();
		System.out.println("Child Enhanced Version.");
	}
}

4、返回的對象的類可以隨着每次調用而發生變化

比起構造器只能返回該類本身,靜態工廠方法顯得靈活的多。
其完全是我們可控的,只要是已聲明的子類型都是允許的。
返回的類對象可能隨着發行版本的不同而不同。

5、返回實例的類可以在編寫方法時不存在

返回的子類可以在編寫靜態工廠方法時不存在,依賴於JDK提供的SPI機制,可以在需要時通過配置讓JVM發現服務並加載。

可以先編寫工廠方法,沒有實現類也無所謂。

public interface SPIInterface {
	void func();
}

public class SPI {
	public static SPIInterface getInstance(){
		ServiceLoader<SPIInterface> serviceLoader = ServiceLoader.load(SPIInterface.class);
		Iterator<SPIInterface> iterator = serviceLoader.iterator();
		if (iterator.hasNext()) {
			return iterator.next();
		}
		throw new RuntimeException("沒有可用的實現類");
	}

	public static void main(String[] args) {
		SPIInterface instance = SPI.getInstance();
		instance.func();
	}
}

當需要提供服務時,按照SPI的規範來進行配置即可。

創建實現類

public class SPIImpl implements SPIInterface {

	@Override
	public void func() {
		System.out.println("SPI 實現方法....");
	}
}

SPI配置

在resources下新建文件:META-INF/services/unit1.item1.SPIInterface,內容爲:

unit1.item1.SPIImpl

重新運行上面的代碼即可執行服務。

劣勢

1、不能被子類化

構造器私有化後,該類將無法被子類化。

2、較難以發現

相對於傳統的new方式創造對象,靜態工廠方式較難以被發現。
而且在JavaDoc生成的文檔中也沒有像構造器一樣被明確標識出來。

但是可以通過標準的命名習慣來彌補這一不足。

靜態工廠方法命名規範

  • form
    類型轉換方法,它只有單個參數,返回該類型的一個相對應的實例。
    Dated= Date.from(instant) ;

  • of
    聚合方法,帶有多個參數,返回該類型的一個實例,把它們合併起來。
    Set faceCards = EnumSet.of(JACK , QUEEN, KING];

  • valueOf
    比 from 和 of 更煩瑣的一種替代方法。
    BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

  • instance/getInstance
    返回的實例是通過方法的(如有)參數來描述的,但是不能說與參數具有同樣的值。
    StackWalke luke = StackWalke.getinstance(options);

  • create/newInstance
    像instance/getInstance一樣,但create/newInstance能夠確保每次調用都返回一個新的實例。
    Object newArray = Array.newInstance(classObject,arrayLen);

  • getType
    像getInstance一樣,但是在工廠方法處於不同的類中的時候使用。
    FileStore fs = Files.getFileStore(path);

  • newType
    像newInstance一樣,但是在工廠方法處於不同的類中的時候使用。
    BufferedReader br = Files.newBufferedReader(path);

  • type
    getType和newType的精簡版。
    List litany = Collections.list(legacylLtany);

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