設計模式(一) 工廠模式 五種寫法總結
系列開篇瞎BB
設計模式相關的文章學習與總結,一直有意為之,一直又覺得時機不到。
一 是怕自己程式碼經驗還不夠,學習了也不懂,強行理解沒有意義。
二 是怕自己學習了以後總結出來,萬一有不對的地方,誤人子弟。
而在現在的公司摸爬滾打半年後,感覺自己寫程式碼遇到了瓶頸,想寫好寫優雅,卻不清楚這麼寫究竟是自以為優雅 還是真的優雅。或對著看一些系統原始碼、框架原始碼時,不太理解他們這麼寫是為什麼。
於是我開始了學習之路,從比較簡單的工廠模式開刀,看了大概10+篇資料,發現各位大大對工廠模式的各種寫法叫法不一,理解也不一,而且沒有一篇是比較全的收錄各種寫法的。so,這也堅定了我將它總結寫出來的決心,既然每個人的理解都有或多或少的缺失或衝突,那我也總結一份我的理解,呈現出來,供各位看官參考 點評。
一概述:
屬於建立型設計模式,需要生成的物件叫做產品 ,生成物件的地方叫做工廠 。
使用場景:
在任何需要生成複雜物件的地方,都可以使用工廠方法模式。
直接用new可以完成的不需要用工廠模式
個人理解,重點就是這個複雜 (建構函式有很多引數)和 是否可以 直接用new。(不理解這句話的話,看完一圈例子就理解了)
下面逐個介紹我所知道的各種工廠模式以及它們的特點,使用場景,並儘可能的找出JDK SDK裡它們的身影。
二 簡單(靜態)工廠:
一個栗子:
我喜歡吃麵條,抽象一個麵條基類,(介面也可以),這是產品的抽象類。
public abstract class INoodles {
/**
* 描述每種麵條啥樣的
*/
public abstract void desc();
}
先來一份蘭州拉麵(具體的產品類):
public class LzNoodles extends INoodles {
@Override
public void desc() {
System.out.println("蘭州拉麵 上海的好貴 家裡才5 6塊錢一碗");
}
}
程式設計師加班必備也要吃泡麵(具體的產品類):
public class PaoNoodles extends INoodles {
@Override
public void desc() {
System.out.println("泡麵好吃 可不要貪杯");
}
}
還有我最愛吃的家鄉的幹扣面(具體的產品類):
public class GankouNoodles extends INoodles {
@Override
public void desc() {
System.out.println("還是家裡的幹扣面好吃 6塊一碗");
}
}
準備工作做完了,我們來到一家“簡單面館”(簡單工廠類),選單如下:
public class SimpleNoodlesFactory {
public static final int TYPE_LZ = 1;//蘭州拉麵
public static final int TYPE_PM = 2;//泡麵
public static final int TYPE_GK = 3;//幹扣面
public static INoodles createNoodles(int type) {
switch (type) {
case TYPE_LZ:
return new LzNoodles();
case TYPE_PM:
return new PaoNoodles();
case TYPE_GK:
default:
return new GankouNoodles();
}
}
}
簡單面館就提供三種麵條(產品),你說你要啥,他就給你啥。這裡我點了一份幹扣面:
/**
* 簡單工廠模式
*/
INoodles noodles = SimpleNoodlesFactory.createNoodles(SimpleNoodlesFactory.TYPE_GK);
noodles.desc();
輸出:
還是家裡的幹扣面好吃 6塊一碗
特點
1 它是一個具體的類,非介面 抽象類。有一個重要的create()方法,利用if或者 switch建立產品並返回。
2 create()方法通常是靜態的,所以也稱之為靜態工廠。
缺點
1 擴充套件性差(我想增加一種麵條,除了新增一個麵條產品類,還需要修改工廠類方法)
2 不同的產品需要不同額外引數的時候 不支援。
三 另一種簡單工廠(反射):
利用反射Class.forName(clz.getName()).newInstance()
實現的簡單工廠:
public class StaticNoodlesFactory {
/**
* 傳入Class例項化麵條產品類
*
* @param clz
* @param <T>
* @return
*/
public static <T extends INoodles> T createNoodles(Class<T> clz) {
T result = null;
try {
result = (T) Class.forName(clz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
點菜時:
/**
* 另一種簡單工廠
* 利用Class.forName(clz.getName()).newInstance()
*/
System.out.println("=====另一種簡單工廠利用Class.forName(clz.getName()).newInstance()======" +
"\n個人覺得不好,因為這樣和簡單的new一個物件一樣,工廠方法應該用於複雜物件的初始化" +
"\n 這樣像為了工廠而工廠");
//蘭州拉麵
INoodles lz = StaticNoodlesFactory.createNoodles(LzNoodles.class);
lz.desc();
//泡麵
INoodles pm = StaticNoodlesFactory.createNoodles(PaoNoodles.class);
pm.desc();
輸出:
=====另一種簡單工廠利用Class.forName(clz.getName()).newInstance()======
個人覺得不好,因為這樣和簡單的new一個物件一樣,工廠方法應該用於複雜物件的初始化
這樣像為了工廠而工廠
蘭州拉麵 上海的好貴 家裡才5 6塊錢一碗
泡麵好吃 可不要貪杯
特點
1 它也是一個具體的類,非介面 抽象類。但它的create()方法,是利用反射機制生成物件返回,好處是增加一種產品時,不需要修改create()的程式碼。
缺點
這種寫法粗看牛逼,細想之下,不談reflection的效率還有以下問題:
1 個人覺得不好,因為Class.forName(clz.getName()).newInstance()呼叫的是無參建構函式生成物件,它和new Object()是一樣的性質,而工廠方法應該用於複雜物件的初始化 ,當需要呼叫有參的建構函式時便無能為力了,這樣像為了工廠而工廠。
2 不同的產品需要不同額外引數的時候 不支援。
四 多方法工廠(常用)
使用方法二 三實現的工廠,都有一個缺點:不同的產品需要不同額外引數的時候 不支援。
而且如果使用時傳遞的type、Class出錯,將不能得到正確的物件,容錯率不高。
而多方法的工廠模式為不同產品,提供不同的生產方法,使用時 需要哪種產品就呼叫該種產品的方法,使用方便、容錯率高。
工廠如下:
public class MulWayNoodlesFactory {
/**
* 模仿Executors 類
* 生產泡麵
*
* @return
*/
public static INoodles createPm() {
return new PaoNoodles();
}
/**
* 模仿Executors 類
* 生產蘭州拉麵
*
* @return
*/
public static INoodles createLz() {
return new LzNoodles();
}
/**
* 模仿Executors 類
* 生產幹扣面
*
* @return
*/
public static INoodles createGk() {
return new GankouNoodles();
}
}
使用時:
/**
* 多方法靜態工廠(模仿Executor類)
*/
System.out.println("==============================模仿Executor類==============================" +
"\n 這種我比較青睞,增加一個新麵條,只要去增加一個static方法即可,也不修改原方法邏輯");
INoodles lz2 = MulWayNoodlesFactory.createLz();
lz2.desc();
INoodles gk2 = MulWayNoodlesFactory.createGk();
gk2.desc();
輸出:
==============================模仿Executor類==============================
這種我比較青睞,增加一個新麵條,只要去增加一個static方法即可,也不修改原方法邏輯
蘭州拉麵 上海的好貴 家裡才5 6塊錢一碗
還是家裡的幹扣面好吃 6塊一碗
原始碼撐腰環節
檢視java原始碼:java.util.concurrent.Executors
類便是一個生成Executor
的工廠 ,其採用的便是 多方法靜態工廠模式:
例如ThreadPoolExecutor
類構造方法有5個引數,其中三個引數寫法固定,前兩個引數可配置,如下寫。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
又如JDK想增加建立ForkJoinPool
類的方法了,只想配置parallelism
引數,便在類裡增加一個如下的方法:
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
這個例子可以感受到工廠方法的魅力了吧:方便建立 同種型別的 複雜引數 物件。
五 普通工廠
普通工廠就是把簡單工廠中具體的工廠類,劃分成兩層:抽象工廠層+具體的工廠子類層。(一般->特殊)
麵條工廠(抽象工廠類),作用就是生產麵條:
public abstract class NoodlesFactory {
public abstract INoodles create();
}
蘭州拉麵工廠 (具體工廠子類):
public class LzFactory extends NoodlesFactory {
@Override
public INoodles create() {
return new LzNoodles();
}
}
泡麵工廠 (具體工廠子類):
public class PaoFactory extends NoodlesFactory {
@Override
public INoodles create() {
return new PaoNoodles();
}
}
最愛的幹扣面工廠 (具體工廠子類):
public class GankouFactory extends NoodlesFactory {
@Override
public INoodles create() {
return new GankouNoodles();
}
}
使用時:
/**
* 普通工廠方法:
*/
System.out.println("===========================普通工廠方法==============================" +
"\n 這種要多寫一個類,不過更面向物件吧 = = ,實際中我更傾向於使用【模仿Executor類】的方式");
NoodlesFactory factory1 = new GankouFactory();
INoodles gk3 = factory1.create();
gk3.desc();
輸出:
===========================普通工廠方法==============================
這種要多寫一個類,不過更面向物件吧 = = ,實際中我更傾向於使用【模仿Executor類】的方式
還是家裡的幹扣面好吃 6塊一碗
普通工廠與簡單工廠模式的區別:
可以看出,普通工廠模式特點:不僅僅做出來的產品要抽象, 工廠也應該需要抽象。
工廠方法使一個產品類的例項化延遲到其具體工廠子類.
工廠方法的好處就是更擁抱變化。當需求變化,只需要增刪相應的類,不需要修改已有的類。
而簡單工廠需要修改工廠類的create()方法,多方法靜態工廠模式需要增加一個靜態方法。
缺點:
引入抽象工廠層後,每次新增一個具體產品類,也要同時新增一個具體工廠類,所以我更青睞 多方法靜態工廠。
六 抽象工廠:
以上介紹的工廠都是單產品系的。抽象工廠是多產品系 (貌似也有產品家族的說法)。
舉個例子來說,每個店(工廠)不僅僅賣麵條,還提供飲料賣。
提供飲料賣,飲料是產品,先抽象一個產品類,飲料:
public abstract class IDrinks {
/**
* 描述每種飲料多少錢
*/
public abstract void prices();
}
然後實現兩個具體產品類:
可樂:
public class ColaDrinks extends IDrinks {
@Override
public void prices() {
System.out.println("可樂三塊五");
}
}
屌絲還是多喝水吧:
public class WaterDrinks extends IDrinks {
@Override
public void prices() {
System.out.println("和我一樣的窮鬼都喝水,不要錢~!");
}
}
抽象飯店,無外乎吃喝(抽象工廠類):
public abstract class AbstractFoodFactory {
/**
* 生產麵條
*
* @return
*/
public abstract INoodles createNoodles();
/**
* 生產飲料
*/
public abstract IDrinks createDrinks();
}
蘭州大酒店(具體工廠類):
public class LzlmFoodFactory extends AbstractFoodFactory {
@Override
public INoodles createNoodles() {
return new LzNoodles();//賣蘭州拉麵
}
@Override
public IDrinks createDrinks() {
return new WaterDrinks();//賣水
}
}
KFC(具體工廠類):
public class KFCFoodFactory extends AbstractFoodFactory {
@Override
public INoodles createNoodles() {
return new PaoNoodles();//KFC居然賣泡麵
}
@Override
public IDrinks createDrinks() {
return new ColaDrinks();//賣可樂
}
}
使用:
/**
* 抽象工廠方法:
*/
System.out.println("==============================抽象方法==============================" +
"\n 老實說,以我這一年的水平我體會不到抽象工廠有何巨大優勢,所以在我這裡我沒有想到很好的使用場景。希望以後在慢慢體會吧。");
AbstractFoodFactory abstractFoodFactory1 = new KFCFoodFactory();
abstractFoodFactory1.createDrinks().prices();
abstractFoodFactory1.createNoodles().desc();
abstractFoodFactory1= new LzlmFoodFactory();
abstractFoodFactory1.createDrinks().prices();
abstractFoodFactory1.createNoodles().desc();
輸出:
==============================抽象方法==============================
老實說,以我這一年的水平我體會不到抽象工廠有何巨大優勢,所以在我這裡我沒有想到很好的使用場景。希望以後在慢慢體會吧。
可樂三塊五
泡麵好吃 可不要貪杯
和我一樣的窮鬼都喝水,不要錢~!
蘭州拉麵 上海的好貴 家裡才5 6塊錢一碗
小結:
將工廠也抽象了,在使用時,工廠和產品都是面向介面程式設計,OO(面向物件)的不得了。
缺點
但是將工廠也抽象後,有個顯著問題,就是類爆炸了。而且每次拓展新產品種類,例如不僅賣吃賣喝,我還想賣睡,提供床位服務,這需要修改抽象工廠類,因此所有的具體工廠子類,都被牽連,需要同步被修改。
老實說,以我這一年的水平我體會不到抽象工廠有何巨大優勢,所以在我這裡我沒有想到很好的使用場景。希望以後在慢慢體會吧。如有您知道,希望不吝賜教。
七 個人總結和使用場景
一句話總結工廠模式:方便建立 同種產品型別的 複雜引數 物件
工廠模式重點就是適用於 構建同產品型別(同一個介面 基類)的不同物件時,這些物件new很複雜,需要很多的引數,而這些引數中大部分都是固定的,so,懶惰的程式設計師便用工廠模式封裝之。
(如果構建某個物件很複雜,需要很多引數,但這些引數大部分都是“不固定”的,應該使用Builder模式)
為了適應程式的擴充套件性,擁抱變化,便衍生出了 普通工廠、抽象工廠等模式。