設計模式(三) 工廠模式
概述:
屬於創建型設計模式,需要生成的對象叫做產品 ,生成對象的地方叫做工廠 。
使用場景:
在任何需要生成復雜對象的地方,都可以使用工廠方法模式。
直接用new可以完成的不需要用工廠模式
下面將介紹五種工廠類:
簡單(靜態)工廠:
一個栗子:
我喜歡吃面條,抽象一個面條基類,(接口也可以),這是產品的抽象類。
public abstract class INoodles { /** * 描述每種面條啥樣的 */ public abstract void desc(); }
先來一份蘭州拉面(具體的產品類)
public class LzNoodles extendsINoodles { @Override public void desc() { System.out.println("蘭州拉面 上海的好貴 家裏才5 6塊錢一碗"); } }
程序員加班必備也要吃泡面(具體的產品類):
public class PaoNoodles extends INoodles { @Override public void desc() { System.out.println("泡面好吃 可不要貪杯"); } }
還有我最愛吃的家鄉的幹扣面(具體的產品類):
publicclass 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;//泡面 publicstatic 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();
特點
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();
特點
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();
源碼撐腰環節
查看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();
普通工廠與簡單工廠模式的區別:
可以看出,普通工廠模式特點:不僅僅做出來的產品要抽象, 工廠也應該需要抽象。
工廠方法使一個產品類的實例化延遲到其具體工廠子類.
工廠方法的好處就是更擁抱變化。當需求變化,只需要增刪相應的類,不需要修改已有的類。
而簡單工廠需要修改工廠類的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();
小結:
將工廠也抽象了,在使用時,工廠和產品都是面向接口編程,OO(面向對象)的不得了。
缺點
但是將工廠也抽象後,有個顯著問題,就是類爆炸了。而且每次拓展新產品種類,例如不僅賣吃賣喝,我還想賣睡,提供床位服務,這需要修改抽象工廠類,因此所有的具體工廠子類,都被牽連,需要同步被修改。
老實說,以我這一年的水平我體會不到抽象工廠有何巨大優勢,所以在我這裏我沒有想到很好的使用場景。
個人總結和使用場景
一句話總結工廠模式:方便創建 同種產品類型的 復雜參數 對象
工廠模式重點就是適用於 構建同產品類型(同一個接口 基類)的不同對象時,這些對象new很復雜,需要很多的參數,而這些參數中大部分都是固定的,so,懶惰的程序員便用工廠模式封裝之。
(如果構建某個對象很復雜,需要很多參數,但這些參數大部分都是“不固定”的,應該使用Builder模式)
為了適應程序的擴展性,擁抱變化,便衍生出了 普通工廠、抽象工廠等模式。
設計模式(三) 工廠模式