Java設計模式(5:設計模式的分類及工廠模式詳解)
一、設計模式的分類
總的來說,設計模式可以分為三大類:創建型模式、結構型模式、行為型模式,具體如下圖:
二、工廠模式
工廠模式分為簡單工廠模式、工廠方法模式和抽象工廠模式。其中簡單工廠模式並不屬於23種設計模式,但並不影響它的廣泛使用。在JDK
的源碼當中,就存在著許多這樣的例子。
2.1 簡單工廠模式
我們先來看一段程式碼:
public static void main(String[] args) {
// 日曆類
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("當前時間為:" + simpleDateFormat.format(calendar.getTime()));
calendar.add(Calendar.HOUR,2);
System.out.println("當前時間加了兩個小時後,時間是: " + simpleDateFormat.format(calendar.getTime()));
}
這段程式碼,大家應該比較熟悉,通過對Calendar
的一系列操作,列印出當前時間和當前時間加兩個小時後的時間,這裡我們來看看結果:
結果正和我們想像的一樣,兩次列印出來的時間相隔兩個小時。但我們今天的重點是Calendar calendar = Calendar.getInstance()
這段程式碼,通過getInstance()
方法拿到了Calendar
類的實例。來看看具體的源程式碼:
public static Calendar getInstance(){
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
// 程式碼不全,有興趣的朋友可以去看JDK源碼
private static Calendar createCalendar(TimeZone zone, Locale aLocale){
// 中間的程式碼省略.....
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
// 中間的程式碼省略.....
return cal;
}
可以看出,getInstance()
方法裡面調用了createCalendar()
方法來得到Calendar
類的實例,最後返回給調用者。而createCalendar()
方法中通過switch(){case}
的判斷來返回所對應的Calendar
類的實例,這其實就是簡單工廠模式的一種應用。
看完簡單工廠模式在JDK
中的應用之後,我們來設計一下自己的例子:
小明家新開了一家小工廠,接了一單生意,幫助海爾(Haier)集團生產冰箱,並需要設計相應的方案。小明本身也是程式設計師出身,思考一會後就寫出了下面的程式碼:
/**
* 冰箱
*/
public interface IFridge {
// 生產冰箱
public void createFridge();
}
/**
* 海爾
*/
public class Haier implements IFridge {
@Override
public void createFridge() {
System.out.println("生產海爾冰箱...");
}
}
客戶端調用程式碼:
public static void main(String[] args) {
IFridge iFridge = new Haier();
iFridge.createFridge();
}
看上面的程式碼,父類IFridge
類指向子類Haier
類的引用,應用層需要依賴於Haier
。如果業務擴展,後續增加格力(Gree
)甚至更多,那麼客戶端這裡的程式碼會越來越臃腫。所以,我們要想辦法將這種依賴減弱,將創建IFridge
對象的細節隱藏掉。我們用簡單工廠模式優化一下:
創建Gree
格力類
/**
* 格力
*/
public class Gree implements IFridge {
@Override
public void createFridge() {
System.out.println("生產格力冰箱...");
}
}
創建FridgeFactory
工廠類
/**
* 冰箱工廠
*/
public class FridgeFactory {
// 創建對應的 IFridge 實例
public static IFridge createFridge(String name){
if ("haier".equals(name)){
return new Haier();
} else if ("gree".equals(name)){
return new Gree();
}
return null;
}
}
修改客戶端調用的程式碼:
public static void main(String[] args) {
// 海爾
IFridge haier = FridgeFactory.createFridge("haier");
haier.createFridge();
// 格力
IFridge gree = FridgeFactory.createFridge("gree");
gree.createFridge();
}
這樣來看,雖然程式碼多了,但維護起來以及擴展起來就方便很多,來看一看類圖:
當然,上面的FridgeFactory
程式碼中依舊有些問題,如果我們需要增加生產美的(Midea
)冰箱,那麼我們就需要去修改createFridge()
方法的程式碼,顯然違背了開閉原則,我們來改造一下:
修改FridgeFactory
工廠類
/**
* 冰箱工廠
*/
public class FridgeFactory {
// 創建對應的 IFridge 實例
public static IFridge createFridge(String className){
try {
if (null != className && !"".equals(className)){
// 反射
return (IFridge)Class.forName(className).newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
修改客戶端調用的程式碼
public static void main(String[] args) {
// com.xxx.Haier 換成 自己 項目中 Haier 所在的位置 海爾
IFridge haier = FridgeFactory.createFridge("com.xxx.Haier");
haier.createFridge();
// com.xxx.Gree 換成 自己 項目中 Gree 所在的位置 格力
IFridge gree = FridgeFactory.createFridge("com.xxx.Gree");
gree.createFridge();
}
優化之後,我們再也不需要隨著業務的提升而去修改FridgeFactory
類中的程式碼了。但是依舊有一個問題,createFridge()
方法中的參數是字元串,如果有人亂填怎麼辦,那不就報錯了,所以再來優化一下:
修改FridgeFactory
工廠類
/**
* 冰箱工廠
*/
public class FridgeFactory {
// 創建對應的 IFridge 實例
public static IFridge createFridge(Class<? extends IFridge> clazz){
try {
if (clazz != null){
return clazz.newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
修改客戶端調用的程式碼
public static void main(String[] args) {
// 海爾
FridgeFactory.createFridge(Haier.class).createFridge();
// 格力
FridgeFactory.createFridge(Gree.class).createFridge();
}
再來看一下類圖:
簡單工廠模式雖然好用,但也有它的局限性:工廠類的職責過重,不利於擴展更為複雜產品結構。
2.2 工廠方法模式
定義一個創建對象的介面,但讓實現這個介面的類來決定實例化哪個類,工廠方法讓類的實例化推遲到子類中進行。
在工廠方法模式中用戶只需要關心所需產品對應的工廠,無須關心創建細節,而且加入新的產品符合開閉原則。
隨著小明家新工廠的生意火爆,各類的訂單都紛涌而至,各個牌子的廠家都想讓小明家的工廠生產冰箱,小明無奈只能開了分工廠,並根據客戶的品牌名給工廠取了對應的名字,其中海爾工廠生產海爾的冰箱,格力工廠生產格力的冰箱,美的工廠生產美的的冰箱。用程式碼演化就是下面這般:
IFridgeFactory
類介面
public interface IFridgeFactory {
public IFridge createIFridge();
}
海爾
// 海爾 工廠
public class HaierFactory implements IFridgeFactory {
@Override
public IFridge createIFridge() {
return new Haier();
}
}
格力
// 格力 工廠
public class GreeFactory implements IFridgeFactory {
@Override
public IFridge createIFridge() {
return new Gree();
}
}
美的
/**
* 美的
*/
public class Midea implements IFridge {
@Override
public void createFridge() {
System.out.println("生產美的冰箱...");
}
}
// 美的
public class MideaFactory implements IFridgeFactory {
@Override
public IFridge createIFridge() {
return new Midea();
}
}
客戶端調用:
public static void main(String[] args) {
// 格力
new GreeFactory().createIFridge().createFridge();
// 海爾
new HaierFactory().createIFridge().createFridge();
// 美的
new MideaFactory().createIFridge().createFridge();
}
這裡其實就是細化了工廠,將業務拆分,利用了設計模式原則中的單一職責原則,讓每個品牌對應工廠只干一件事,不去摻和其他品牌的事情。來看一看類圖:
工廠方法模式適用於一下場景:
- 創建對象需要大量重複的程式碼
- 客戶端(應用層)不依賴於產品類實例如何被創建、實現等細節
- 一個類通過其子類來指定創建哪個對象
工廠方法模式也有缺點:
- 類的個數容易過多,增加複雜度
- 增加了系統的抽象性和理解難度
2.3 抽象工廠模式
定義:提供一個創建一系列相關或者相互依賴對象的介面,無需指定他們具體的類。
這個定義讀起來相當的拗口,很抽象,不好理解。還是和上面的例子結合來說明:
在生產完一批冰箱並上市售賣之後,美的、格力、海爾等公司非常滿意,慢慢的將自己家的空調、熱水器也交給小明家的工廠去生產了。小明為此在對應的品牌工廠有開闢了對應的生產設備的空間(這裡為了大家看的方便,我將所有的程式碼都放上去):
冰箱、空調、熱水器介面
// 冰箱
public interface IFridge {
// 生產冰箱
public void createFridge();
}
// 空調
public interface IAirConditioner {
// 生產空調
public void createAirConditioner();
}
// 熱水器
public interface IWaterHeater {
// 生產熱水器
public void createWaterHeater();
}
海爾
/**
* 海爾 冰箱
*/
public class HaierFridge implements IFridge{
@Override
public void createFridge() {
System.out.println("生產海爾冰箱...");
}
}
// 海爾 空調
public class HaierAirConditioner implements IAirConditioner {
@Override
public void createAirConditioner() {
System.out.println("生產海爾空調...");
}
}
// 海爾熱水器
public class HaierWaterHeater implements IWaterHeater {
@Override
public void createWaterHeater() {
System.out.println("生產海爾熱水器...");
}
}
格力
/**
* 格力 冰箱
*/
public class GreeFridge implements IFridge {
@Override
public void createFridge() {
System.out.println("生產格力冰箱...");
}
}
// 格力 空調
public class GreeAirConditioner implements IAirConditioner {
@Override
public void createAirConditioner() {
System.out.println("生產格力空調...");
}
}
// 格力熱水器
public class GreeWaterHeater implements IWaterHeater {
@Override
public void createWaterHeater() {
System.out.println("生產格力熱水器...");
}
}
美的
/**
* 美的 冰箱
*/
public class MideaFridge implements IFridge{
@Override
public void createFridge() {
System.out.println("生產美的冰箱...");
}
}
// 美的 空調
public class MideaAirConditioner implements IAirConditioner {
@Override
public void createAirConditioner() {
System.out.println("生產美的空調...");
}
}
// 美的熱水器
public class MideaWaterHeater implements IWaterHeater {
@Override
public void createWaterHeater() {
System.out.println("生產美的熱水器...");
}
}
工廠介面
public interface IFactory {
// 冰箱
public IFridge createIFridge();
// 空調
public IAirConditioner createIConditioner();
// 熱水器
public IWaterHeater createIWaterHeater();
}
海爾工廠
// 海爾 工廠
public class HaierFactory implements IFactory {
// 冰箱
@Override
public IFridge createIFridge() {
return new HaierFridge();
}
// 空調
@Override
public IAirConditioner createIConditioner() {
return new HaierAirConditioner();
}
// 熱水器
@Override
public IWaterHeater createIWaterHeater() {
return new HaierWaterHeater();
}
}
格力工廠
// 格力
public class GreeFactory implements IFactory {
// 冰箱
@Override
public IFridge createIFridge() {
return new GreeFridge();
}
// 空調
@Override
public IAirConditioner createIConditioner() {
return new GreeAirConditioner();
}
// 熱水器
@Override
public IWaterHeater createIWaterHeater() {
return new GreeWaterHeater();
}
}
美的工廠
// 美的
public class MideaFactory implements IFactory {
// 冰箱
@Override
public IFridge createIFridge() {
return new MideaFridge();
}
// 空調
@Override
public IAirConditioner createIConditioner() {
return new MideaAirConditioner();
}
// 熱水器
@Override
public IWaterHeater createIWaterHeater() {
return new MideaWaterHeater();
}
}
客戶端調用
public static void main(String[] args) {
// 海爾工廠
HaierFactory haierFactory = new HaierFactory();
haierFactory.createIFridge().createFridge();
haierFactory.createIConditioner().createAirConditioner();
haierFactory.createIWaterHeater().createWaterHeater();
// 格力工廠
GreeFactory greeFactory = new GreeFactory();
greeFactory.createIFridge().createFridge();
greeFactory.createIConditioner().createAirConditioner();
greeFactory.createIWaterHeater().createWaterHeater();
// 美的工廠
MideaFactory mideaFactory = new MideaFactory();
mideaFactory.createIFridge().createFridge();
mideaFactory.createIConditioner().createAirConditioner();
mideaFactory.createIWaterHeater().createWaterHeater();
}
類圖
從上面一大堆的程式碼,尤其是類圖,我們可以很明顯的感覺到,抽象工廠可以完美清晰的描述海爾、格力、美的三個品牌的冰箱、空調、熱水器的龐大體系。但也正因為如此,抽象工廠給我們的視覺衝擊有些大,能很明顯的感覺到系統的複雜性、抽象性以及系統的極難擴展性;並且這裡還隱藏著一個違背開閉原則的問題:
在工廠介面
IFactory
類中,如果在日後的產品升級當中,需要增加生產洗衣機的業務,那這裡修改之後,所有實現IFactory
介面的類都需要變動,很大程度增加了系統的不穩定性。
也正因為如此,在實際的業務開發中,我們不應該有著強烈的強迫症和潔癖,認為一個系統的結構設計必須要完美的符合各種原則。要結合實際的業務去思考,如果系統結構的等級更新不頻繁的話,不遵守某些原則也是有必要性的,畢竟所有的技術都是為業務而服務的。