Java反射機制demo(七)—反射機制與工廠模式
工廠模式
簡介
工廠模式是最常用的實例化對象模式。
工廠模式的主要作用就是使用工廠方法代替new操作。
為什么要使用工廠模式?直接new不好嗎?
直接new沒有什么不好,只是工廠模式可以給系統帶來更好的可擴展性和盡量少的修改量。
分類
工廠模式一般有兩類,一類是工廠方法模式,另一類是抽象工廠模式。但是《head first 設計模式》中,通過某些例子,實際上把工廠模式分為三種:
- 簡單工廠模式(Simple Factory)
- 工廠方法模式(Factory Method)
- 抽象工廠模式(Abstract Factory)
其中簡單工廠模式是工廠方法模式的一種特例。
使用情況
- 在編碼時不能預見需要創建哪種類的實例
- 系統不應依賴於產品類實例如何被創建,組合和表達的細節。
工廠方法模式組成
- 抽象工廠角色:這是工廠方法模式的核心,它與應用程序無關。是具體工廠角色必須實現的接口或者必須繼承的父類。在java中它由抽象類或者接口來實現。
- 具體工廠角色:它含有和具體業務邏輯有關的代碼。由應用程序調用以創建對應的具體產品的對象。在java中它由具體的類來實現。
- 抽象產品角色:它是具體產品繼承的父類或者是實現的接口。在java中一般有抽象類或者接口來實現。
- 具體產品角色:具體工廠角色所創建的對象就是此角色的實例。在java中由具體的類來實現。
- 客戶端:調用工廠類去產生實例,並調用這些實例的方法進行相應的工作。
簡單工廠模式實際上是一種靜態的工廠方法模式。簡單工廠模式由一個工廠類根據傳入的參數決定創建哪一種的產品類。
現在先給出一個沒有使用反射機制的工廠模式。
當你在玩最近流行的游戲,英雄聯盟LOL或者是DOTA/DOTA2,你和隊友包括敵方陣營都會選擇英雄。這個英雄的選擇過程我們可以看做是一個new一個英雄。例如我要選擇(new)一個暗夜刺客,然后我就得到了一個暗夜刺客的實例,接着通過這個實例進行游戲。下面的例子,用簡單工廠模式來生產英雄模擬以上過程。
簡單工廠模式DEMO:
首先,完成一個英雄的接口,這對應了product接口,即抽象的產品類。
package com.aaron.reflect.factory; public interface Hero { public void say(); }
然后用幾個不同的類去實現這個接口,這些類就是實際上產品的生產者。
package com.aaron.reflect.factory; public class EarthShaker implements Hero { @Override public void say() { System.out.println("撼地神牛:Do not push me or I will impale you on my horns."); } } //------------------------分割線------------------------ package com.aaron.reflect.factory; public class NeverMore implements Hero { @Override public void say() { System.out.println("影魔:What, mortal?"); } }
然后,我們實現一個靜態的工廠方法,在這個工廠類中,靜態地得到產品的實例
package com.aaron.reflect.factory; public class HeroesFactory { public static Hero choose(String shortName){ Hero hero = null; if(shortName.equals("ES")){ hero = new EarthShaker(); } else if(shortName.equals("SF")) { hero = new NeverMore(); } return hero; } }
測試一下:
package com.aaron.reflect.factory; public class FactoryTest { public static void main(String[] args) { HeroesFactory.choose("ES").say(); HeroesFactory.choose("SF").say(); } }
運行結果:
撼地神牛:Do not push me or I will impale you on my horns. 影魔:What, mortal?
OK,一個簡單完整的簡單工廠模式就實現了。
工廠方法模式只是把這里的工廠類再次抽象,抽象出一個工廠接口,當使用ES的時候就新建一個ESFactory的實現,使用SF時,就新建一個SFFactory。這樣看似麻煩,但是當業務復雜時卻能保證頻繁的變更不會導致系統越對越亂,只需要添加一個產品,添加一個用來生產產品的工廠。
使用反射機制修改工廠模式
然而,這種更改仍然要耗費很多精力,除了接口其余的我們都更改了。
如上面的簡單工廠模式中,如果我們要新增一個英雄,屠夫,Pudge。我們需要做如下更改:
新增Pudge類
修改Factory類,增加Pudge的匹配
新增Pudge代碼:
package com.aaron.reflect.factory; public class Pudge implements Hero { @Override public void say() { System.out.println("屠夫:Ah! Fresh meat!"); } }
增加Pudge匹配的代碼
package com.aaron.reflect.factory; public class HeroesFactory { public static Hero choose(String shortName){ Hero hero = null; if(shortName.equals("ES")){ hero = new EarthShaker(); } else if(shortName.equals("SF")) { hero = new NeverMore(); } else if (shortName.equals("TF")) { hero = new Pudge(); } return hero; } }
此時,如果我們把一百多個英雄都添加進去,我們就會修改一百多次工廠類。
現在我們可以使用反射機制,來避免這種麻煩。
使用反射機制改寫工廠類:
package com.aaron.reflect.factory; public class HeroesFactory { public static Hero choose(String shortName){ Hero hero = null; try { hero = (Hero) Class.forName(shortName).newInstance(); } catch (Exception e) { e.printStackTrace(); } return hero; } }
代碼寫到這里,問題就來了。
Class.forName(String str)這個方法的參數必須是包含報名的,例如我想得到一個ES撼地神牛,我僅僅傳入“ES”必然不行,傳入“EarthShaker”這個類名也找不到對應的類,只有傳入完整的包名和類名,"com.aaron.reflect.factory.HeroesFactory"。
怎么來解決呢?其中一個辦法是引入properties配置文件。
先看一下properties文件;
ES=EarthShaker SF=NeverMore TF=Pudge
修改工廠類:
package com.aaron.reflect.factory; import java.io.FileInputStream; import java.util.Properties; public class HeroesFactory { public static Hero choose(String shortName) { Hero hero = null; // 從properties文件中讀取shortName對應的完整包名 Properties properties = new Properties(); try { properties.load(new FileInputStream("src/nameMapping.properties")); String fullName = properties.getProperty(shortName); hero = (Hero) Class.forName(fullName).newInstance(); } catch (Exception e) { e.printStackTrace(); } return hero; } }
這么做就一勞永逸了。
當新增一個產品類時,工廠類就不需要做任何改動了。
但是,程序寫到這,應該想到一個很嚴重的問題,如果工廠類被頻繁調用時,沒新建一個產品,就要讀一次propert ies文件,盡管java對properties的支持非常便捷,但是這么頻繁地去操作IO明顯在性能上有很大的弱點。
在測試類中,加入計時,得到運行結果如下:
撼地神牛:Do not push me or I will impale you on my horns. 影魔:What, mortal? 屠夫:Ah! Fresh meat! 耗時:6ms
平均耗時5毫秒左右。
這樣的話,我們對程序做以下改動。
package com.aaron.reflect.factory; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; public class HeroesFactory { public static Properties init() { // 從properties文件中讀取shortName對應的完整包名 Properties properties = new Properties(); try { properties.load(new FileInputStream("src/nameMapping.properties")); } catch (IOException e) { e.printStackTrace(); } return properties; } public static Hero choose(String shortName) { Hero hero = null; try { String fullName = HeroesFactory.init().getProperty(shortName); hero = (Hero) Class.forName(fullName).newInstance(); } catch (Exception e) { e.printStackTrace(); } return hero; } }
然后看一下運行結果:
撼地神牛:Do not push me or I will impale you on my horns. 影魔:What, mortal? 屠夫:Ah! Fresh meat! 耗時:3ms
平均3ms左右。當然這跟機器的性能也有些關系。當然,大部分的耗時應該還是花在了I/O讀文件和打印輸出上。
拋出這些技術細節問題,
總之,反射機制給工廠模式帶來了新的體驗。