設計模式是軟件工程中一些問題的統一解決方案的模型,它的出現是爲了解決一些廣泛存在的,卻不能被語言特性直接解決的問題,隨着軟件工程的發展,設計模式也會不斷的進行更新,本文介紹的是經典設計模式-簡單工廠模式以及來自java8的lambda的對它的優化。html
定義一個工廠類,對實現了同一接口的一些類進行實例的建立。簡單工廠模式的實質是由一個工廠類根據傳入的參數,動態決定應該建立哪個產品類(這些產品類繼承自一個父類或接口)的實例java
個人理解是工廠模式比如一個容器,裏面裝了有許多共同特徵的對象,通提供過工廠對外提供的方法向外提供實例化子類的功能,和現實的中的工廠很像。簡明點說,是許多對象的集合,根據需求對外提供不一樣的對象。git
在寫了幾個設計模式的博客以後我發現每次都要虛構一個不存在的例子很費腦筋,因而我決定後面的例子用我日常喜歡玩的一些遊戲來描述,感受會更有意思:)
在一片古老的魔法大陸上,有許多隱世的祕寶等待探險者去挖掘,可這樣的機會每每也伴隨着危險,因此探險者們每每須要結伴而行,通常來講,一個不會在野外直接當掉的隊伍至少須要保證三種類型的職業(坦克,輸出,治療,俗稱'鐵三角')。所以,在這樣的需求下,長此以往,魔法大路上誕生了一家'冒險者僱傭兵工廠',沒有人知道這家工廠是什麼時候誕生,也不知道里面究竟有怎樣的實力...只是知道,你給它錢,和你須要的職業,它就會提供一個對應職業的僱傭兵助你完成此次冒險....
有一天,有一個戰士(坦克)阿呆收到消息,有一個叫作'火焰洞窟'裏面可能有好東西,可他身邊沒有夥伴一我的顯然是不能去送死的,因而爲了快速湊到夥伴,他想到了僱傭兵工廠...他須要一個可以釋放冰霜法術的法師(輸出)(冰屬性能夠剋制火焰洞窟裏的怪物)和一個可以療傷的牧師(治療)這兩個職業,下面在客戶端中模擬場景github
首頁抽象坦克,輸出,治療爲探險者接口,提供一個戰鬥的技能的方法
探險者接口設計模式
public interface adventurer { /** * 使用戰鬥技能 */ void useBattleSkill(); }
戰士,冰霜法師,牧師實現探險者接口,做爲子類提供不一樣的戰鬥技能實現
戰士類ide
public class warrior implements adventurer { @Override public void useBattleSkill() { System.out.println("盾牌格擋!"); } }
冰霜法師類函數
public class frostMage implements adventurer { @Override public void useBattleSkill() { System.out.println("寒冰箭!"); } }
牧師類優化
public class priests implements adventurer { @Override public void useBattleSkill() { System.out.println("快速治療!"); } }
冒險者工廠類,根據不一樣的職業需求實例化不一樣的冒險者給客戶端this
public class adventFactory { public static adventurer createAdventurer(String professionType) { adventurer adventurer; switch (professionType) { case "戰士": adventurer = new warrior(); break; case "冰霜法師": adventurer = new frostMage(); break; case "牧師": adventurer = new priests(); break; default: throw new IllegalArgumentException("咱們沒這種職業!"); } return adventurer; } }
客戶端類,模擬三個職業進入火焰洞窟並使用各自的技能設計
public class Client { public static void main(String[] args) { //經過冒險者工廠實例化出戰士,冰霜法師,牧師 adventurer warrior = adventFactory.createAdventurer("戰士"); adventurer frostMage = adventFactory.createAdventurer("冰霜法師"); adventurer priest = adventFactory.createAdventurer("牧師"); //進入火焰洞窟 System.out.println("================進入火焰洞窟================"); warrior.useBattleSkill(); frostMage.useBattleSkill(); priest.useBattleSkill(); } }
控制檯結果
================進入火焰洞窟================ 盾牌格擋! 寒冰箭! 快速治療!
如同上文所講,僱傭兵工廠經過switch語句根據不一樣的輸出實例化不一樣的對象給客戶端調用,這樣客戶端只須要和工廠打交道,有什麼需求提供給工廠,工廠實例化出對應對象返回,因此工廠能夠理解爲是對象實例化的集合。
爲了增長趣味性(主要是我本身的..編例子很無聊T_T),本文使用了MMORPG遊戲的鐵三角的組隊進副本的例子,冒險者工廠爲冒險者提供不一樣職業的冒險者,冒險者不須要與具體的同伴溝通,經過工廠就能夠完成需求,能夠說是將需求者與僱傭兵這兩類人給解耦了,經過冒險工廠來交互。從封裝角度來講,以前寫的命令模式,策略模式都是對行爲的封裝,而工廠模式是對對象構造器的封裝,這一點也爲後面的lambda的優化選擇接口提供了依據。
下面是uml圖
前面提到簡單工廠模式的封裝模式是對對象的構造進行封裝,那麼若是採用函數接口替換switch語句的話,選擇的函數應該是Supplier<T>
(無參構造函數) 或者Funtion<T,R>
(有參構造函數),這裏咱們選擇無參構造函數來進行優化,使用Map存儲這些構造方法,並利用函數語言的懶加載特性,使得直到真正調用實例化對象的某一方法時,才真正調用構造函數,代碼以下。
使用supplier封裝構造器優化後的Factory類
public class adventFactory { private static final Map<String, Optional<Supplier<adventurer>>> MAP = new ConcurrentHashMap<>(); static { MAP.put("戰士", Optional.of(warrior::new)); MAP.put("冰霜法師", Optional.of(frostMage::new)); MAP.put("牧師", Optional.of(priests::new)); } public static adventurer createAdventurer(String professionType) { //get(professionType)得到optional對象,orElseThrow用於防止或者異常參數,get()及早求值,執行對象的實例化,直到這一步函數才真正的執行 return MAP.get(professionType) .orElseThrow(() -> new IllegalArgumentException("咱們工廠沒這種職業!")) .get(); } }
客戶端代碼與原先如出一轍,這裏就不顯示了,下面說明一下這個Factory類。
使用supplier函數接口將構造器封裝,並存儲在MAP中,注意這裏與傳統的直接存實例好的對象進去不一樣,這裏存儲的只是構造過程,並不會真正的佔用空間,除非客戶端調用create方法須要這個對象了,纔會實例化出來,這裏利用了函數的懶加載特性。同時爲了防止可惡的空指針異常或者是需求並不存在的類,在supplier的基礎上使用了optional類進行包裝,避免了各種if判斷,能夠看出使用了lambda優化以後,已經不存在任何的條件判斷語句(switch,if)了,將面向對象與函數語言特性相結合,感受很不錯。
前面提到可優化點的時候提到了簡單工廠方法違背了開閉原則,然而通過lambda優化以後的方式雖然消除了switch與if分支,可是彷佛並無克服這個問題,工廠類依舊是違背這個原則的,那麼可不可能再次優化呢?我認爲這種須要傳入魔法值來作一些事情的方法或者設計模式,枚舉都是一個不錯的選擇,下面嘗試使用枚舉。
使用枚舉變量封裝這些構造器,這樣不只可使得工廠能夠將修改關閉,同時也省去了optional類的包裝,由於你傳入的參數只能是枚舉變量已經定義好的。下面是代碼。
枚舉類,內部存一個supplier對象,存放各大職業的構造器,對外暴露getConstructor方法進行實例化
public enum adventEnum { WARRIOR(warrior::new), MAGE_FROST(frostMage::new), PRIESTS(priests::new); private final Supplier<adventurer> constructor; adventEnum(Supplier<adventurer> constructor) { this.constructor = constructor; } public Supplier<adventurer> getConstructor() { return constructor; } }
工廠類
public class adventFactory { public static adventurer createAdventurer(adventEnum adventEnum) { adventEnum.getConstructor().get(); } }
工廠類十分簡潔,然而不只簡潔,還完美繼承了上面的全部優點,而且克服了劣勢。
客戶端
import static com.lambda.enums.adventEnum.*; public class Client { public static void main(String[] args) { //經過冒險者工廠實例化出戰士,冰霜法師,牧師 adventurer warrior = adventFactory.createAdventurer(WARRIOR); adventurer frostMage = adventFactory.createAdventurer(MAGE_FROST); adventurer priest = adventFactory.createAdventurer(PRIESTS); //進入火焰洞窟 System.out.println("================進入火焰洞窟================"); warrior.useBattleSkill(); frostMage.useBattleSkill(); priest.useBattleSkill(); } }
客戶端的調用參數變成了枚舉類,這裏靜態導入枚舉類,我一直以爲使用枚舉變量的代碼擁有一種自注釋的特性,即不需寫註釋就能夠看的很明瞭。
麻雀雖小,五臟俱全,例子很簡單,可是最後的成果是面嚮對象語言+函數式語言+枚舉
的結合,能夠看到這種組合效果是十分棒的,代碼不只簡潔易用性高同時還保持了健壯性與可擴展性,但願你們能夠多嘗試,我認爲多種語言範式的組合的語言多是第三代語言或者更新的語言發展的趨勢吧(Scala,C#等)^_^,你們下篇再見。
本文的代碼與md文章同步更新在github中的simple-factory-mode模塊下,歡迎fork :)