讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議88:用枚舉實現工廠方法模式更簡潔 >

建議88:用枚舉實現工廠方法模式更簡潔

工廠方法模式(Factory Method Pattern)是「創建對象的接口,讓子類決定實例化哪一個類,並使一個類的實例化延遲到其子類」。工廠方法模式在我們的開發工作中經常會用到。下面以汽車製造為例,看看一般的工廠方法模式是如何實現的,代碼如下:


//抽像產品

interface Car{

};

//具體產品類

class FordCar implements Car{

};

//具體產品類

class BuickCar implements Car{

};

//工廠類

class CarFactory{

//生產汽車

public static Car createCar(Class<?extends Car>c){

try{

return(Car)c.newInstance();

}catch(Exception e){

e.printStackTrace();

}

return null;

}

}


這是最原始的工廠方法模式,有兩個產品:福特汽車和別克汽車,然後通過工廠方法模式來生產。有了工廠方法模式,我們就不用關心一輛車具體是怎麼生成的了,只要告訴工廠「給我生產一輛福特汽車」就可以了,下面是產出一輛福特汽車時客戶端的代碼:


public static void main(Stringargs){

//生產車輛

Car car=CarFactory.createCar(FordCar.class);

}


這就是我們經常使用的工廠方法模式,但經常使用並不代表就是最優秀、最簡潔的。此處再介紹一種通過枚舉實現工廠方法模式的方案,誰優誰劣你自行評價。枚舉實現工廠方法模式有兩種方法:

(1)枚舉非靜態方法實現工廠方法模式

我們知道每個枚舉項都是該枚舉的實例對象,那是不是定義一個方法可以生成每個枚舉項的對應產品來實現此模式呢?代碼如下:


enum CarFactory{

//定義工廠類能生產汽車的類型

FordCar, BuickCar;

//生產汽車

public Car create(){

switch(this){

case FordCar:

return new FordCar();

case BuickCar:

return new BuickCar();

default:

throw new AssertionError(\"無效參數\");

}

}

}


create是一個非靜態方法,也就是只有通過FordCar、BuickCar枚舉項才能訪問。採用這種方式實現工廠方法模式時,客戶端要生產一輛汽車就很簡單了,代碼如下:


public static void main(Stringargs){

//生產汽車

Car car=CarFactory.BuickCar.create();

}


(2)通過抽像方法生成產品

枚舉類型雖然不能繼承,但是可以用abstract修飾其方法,此時就表示該枚舉是一個抽像枚舉,需要每個枚舉項自行實現該方法,也就是說枚舉項的類型是該枚舉的一個子類,我們來看代碼:


enum CarFactory{

FordCar{

public Car create(){

return new FordCar();

}

},

BuickCar{

public Car create(){

return new BuickCar();

}

};

//抽像生產方法

public abstract Car create();

}


首先定義一個抽像製造方法create,然後每個枚舉項自行實現。這種方式編譯後會產生兩個CarFactory的匿名子類,因為每個枚舉項都要實現抽像create方法。客戶端的調用與上一個方案相同,不再贅述。

讀者可能會問:為什麼要使用枚舉類型的工廠方法模式呢?那是因為使用枚舉類型的工廠方法模式有以下三個優點:

(1)避免錯誤調用的發生

一般工廠方法模式中的生產方法(也就是createCar方法)可以接收三種類型的參數:類型參數(如我們的例子)、String參數(生產方法中判斷String參數是需要生產什麼產品)、int參數(根據int值判斷需要生產什麼類型的產品),這三種參數都是寬泛的數據類型,很容易產生錯誤(比如邊界問題、null值問題),而且出現這類錯誤編譯器還不會報警,例如:


public static void main(Stringargs){

//生產車輛

Car car=CarFactory.createCar(Car.class);

}


Car是一個接口,完全合乎createCar方法的要求,所以它在編譯時不會報任何錯誤,但一運行起來就會報InstantiationException異常。而使用枚舉類型的工廠方法模式就不存在該問題了,不需要傳遞任何參數,只需要選擇好生產什麼類型的產品即可。

(2)性能好,使用便捷

枚舉類型的計算是以int類型的計算為基礎的,這是最基本的操作,性能當然會快,至於使用便捷,注意看客戶端的調用,代碼的字面意思就是「汽車工廠,我要一輛別克汽車,趕快生產」。

(3)降低類間耦合

不管生產方法接收的是Class、String還是int的參數,都會成為客戶端類的負擔,這些類並不是客戶端需要的,而是因為工廠方法的限制必須輸入的,例如Class參數,對客戶端main方法來說,它需要傳遞一個FordCar.class參數才能生產一輛福特汽車,除了在create方法中傳遞該參數外,業務類不需要改Car的實現類。這嚴重違背了迪米特原則(Law of Demeter,簡稱為LoD),也就是最少知識原則:一個對像應該對其他對像有最少的瞭解。

而枚舉類型的工廠方法就沒有這種問題了,它只需要依賴工廠類就可以生產一輛符合接口的汽車,完全可以無視具體汽車類的存在。

注意 下一次,使用枚舉來實現工廠方法模式。