设计模式之反射与配置文件
? ?為了滿足“開閉原則”,大部分設計模式都引入了抽象層,如工廠方法模式、抽象工廠模式、適配器模式、橋接模式、命令模式、策略模式等等。客戶端代碼針對抽象層編程,而在程序運行的時候再指定其子類,根據“里氏代換原則”和面向對象的多態性,子類對象在運行時將覆蓋父類對象。如果需要對系統進行擴展或修改,只需修改子類類名即可。在具體實現時,通過引入配置文件可以使得用戶在不修改任何客戶端代碼的前提下增加或替換子類,其基本實現過程如下:
????? (1)客戶端針對抽象層編程,客戶端代碼中不能出現任何具體類類名,即客戶端不直接實例化對象;
????? (2)引入純文本格式的配置文件,通常是XML文件,將具體類類名存儲在配置文件中;
????? (3)通過DOM(Document Object Model,文檔對象模型)、SAX(Simple APIfor XML)等XML解析技術獲取存儲在配置文件中的類名;
????? (4)在客戶端代碼中通過反射(Reflection)機制根據類名創建對象,用反射所創建的對象替換父類對象的引用,程序運行時,將調用子類方法來實現業務功能;
????? (5)如果需要擴展功能,只需增加一個新的子類繼承抽象父類,再修改配置文件,重新運行程序即可;如果需要替換功能,只需用另一個子類類名替換存儲在配置文件中的原有子類類名即可。無論是擴展還是替換都無須修改既有類庫和客戶端源代碼,完全符合開閉原則。
????? 下面通過工廠方法模式來說明如何使用配置文件和反射機制:
【實例說明】:寶馬(BMW)工廠可以生產寶馬轎車,奔馳(Benz)工廠可以生產奔馳轎車,使用工廠方法模式來設計該場景,所得類圖如圖1所示:
圖1?工廠方法模式實例類圖
???????在圖1中,CarFactory是抽象工廠,聲明了工廠方法produceCar(),在其子類中實現了該方法,用于返回具體的產品。在客戶端代碼中將出現如下代碼:
| CarFactory cf; Car car; cf? = new BMWFactory();? //創建具體工廠 car = cf.produceCar(); //使用工廠方法創建產品對象 car.run(); //調用產品的業務方法 |
????? 在上述代碼中,客戶端針對抽象層編程,但是在創建具體工廠的時候還是涉及到了具體工廠子類類名,注意加粗的代碼行。如果需要更換產品,如將BMW改為Benz,則需要更換工廠,要將BMWFactory改為BenzFactory,這將導致客戶端代碼發生修改。從客戶端的角度而言違反了開閉原則,因此需要對上述代碼進行改進。引入配置文件和反射機制是最佳的改進方法之一。
????? 首先,我們將具體工廠類類名存儲在如下XML文檔中:
| <?xml version="1.0"?> <config> ?????? <className>BMWFactory</className> </config> |
????? 該XML文檔即為配置文件,用于存儲具體類的類名。Spring等主流的業務層框架都使用了XML格式的配置文件。
????? 為了動態創建子類對象,我們需要再設計一個工具類XMLUtil用于讀取該XML配置文件,在此使用Java語言實現該工具類。在XMLUtil的設計中需要使用Java語言的兩個技術點,其一是DOM,即對XML文件的操作,關于DOM的詳細學習可以參考其他相關書籍和資料,在此不予擴展;其二是Java反射機制,下面對Java反射機制做一個簡單的介紹。
???? Java反射(Java Reflection)是指在程序運行時獲取已知名稱的類或已有對象的相關信息的一種機制,包括類的方法、屬性、父類等信息,還包 括實例的創建和實例類型的判斷等。在反射中使用最多的類是Class,Class類的實例表示正在運行的Java應用程序中的類和接口,其forName(StringclassName)方法可以返回與帶有給定字符串名的類或接口相關聯的?Class對象,再通過Class對象的newInstance()方法創建此對象所表示的類的一個新實例,即通過一個類名字符串得到類的實例。如創建一個字符串類型的對象,其代碼如下所示:
| ??? //通過類名生成實例對象并將其返回 ??? Class c=Class.forName("String"); ??? Object obj=c.newInstance(); ??? return obj; |
?????? 此外,在JDK中還提供了java.lang.reflect包,封裝了一些其他與反射相關的類,在本書中只用到上述簡單的反射代碼,在此不予擴展。
????? 通過引入DOM和反射機制后,可以在XMLUtil中實現讀取XML文件并根據存儲在XML文件中的類名創建對應的對象,XMLUtil類的詳細代碼如下:
| import javax.xml.parsers.*; import org.w3c.dom.*; import org.xml.sax.SAXException; import java.io.*; public class XMLUtil { //該方法用于從XML配置文件中提取具體類類名,并返回一個實例對象 ?????? public static? Object getBean() ?????? { ????????????? try ????????????? { ???????????????????? //創建DOM文檔對象 ???????????????????? DocumentBuilderFactory? dFactory = DocumentBuilderFactory.newInstance(); ???????????????????? DocumentBuilder? builder = dFactory.newDocumentBuilder(); ???????????????????? Document? doc;???????????????????????????????????????????????? ???????????????????? doc? = builder.parse(new File("config.xml")); ????????????? ???????????????????? //獲取包含類名的文本節點 ???????????????????? NodeList? nl = doc.getElementsByTagName("className"); ???????????????????? Node? classNode=nl.item(0).getFirstChild(); ????????????????????String cName=classNode.getNodeValue(); ??????????? ? ????????????????? ? //通過類名生成實例對象并將其返回 ?????????????????? ?Class c=Class.forName(cName); ?????? ?? ??? ????? Object obj=c.newInstance(); ??????????????????? return obj; ???????????????}?? ??????????? ? catch(Exception e) ??????????? ? { ??????????? ???????? e.printStackTrace(); ??????????? ???????? return null; ?????????? ? } ?????? } } |
????? 有了XMLUtil類后,我們在客戶端代碼中不再直接使用new關鍵字來創建具體的工廠類,而是將具體工廠類的類名存放在XML文件中,再通過XMLUtil類的靜態工廠方法getBean()方法進行對象的實例化,代碼修改如下:
| CarFactory cf; Car car; cf? = (CarFactory)XMLUtil.getBean();//getBean()的返回類型為Object,此處需要進行強制類型轉換 car = cf.produceCar(); car.run(); |
????? 在C#中實現讀取配置文件和反射更為簡單,我們只需先增加一個XML格式的配置文件,如App.config,代碼如下所示:
| <?xml version="1.0"? encoding="utf-8" ?> <configuration> ?? <appSettings> ???? <add key="factory" value="Demo.CarFactory"/> ?? </appSettings> </configuration> |
????? 在.NET中反射生成對象也很簡單,由于在.NET的程序集中封裝了類型元數據信息,因此可以先通過Assembly的Load("程序集名稱")方法加載一個程序集,再通過其CreateInstance("命名空間.類")方法根據類名創建一個object類型的對象,用戶可以根據需要轉換為所需類型。示意代碼如下:
| //導入命名空間 using System.Reflection; object obj = Assembly.Load("程序集名稱").CreateInstance("命名空間.類"); |
????? 在上述代碼中,“命名空間.類”可以存儲在配置文件中,使用ConfigurationManager類的AppSettings屬性可以獲取存儲在配置文件中的類名字符串。客戶端代碼如下所示:
| CarFactory cf; Car? car; //讀取配置文件 string factoryStr =? ConfigurationManager.AppSettings["factory"];? //反射生成對象,程序集名為Demo cf? =?? (CarFactory)Assembly.Load("Demo").CreateInstance(factoryStr);? car = cf.ProduceCar(); car.Run(); |
???????由于C++語言的特性,在C++中實現類似Java或C#來反射生成對象的過程相對較為復雜,感興趣的讀者可以參考其他相關資料,在此不予擴展。
????? 在引入配置文件和反射機制后,需要更換或增加新的具體類將變得很簡單,只需增加新的具體類并修改配置文件即可,無須對現有類庫和客戶端代碼進行任何修改,完全符合開閉原則。在很多設計模式中都可以通過引入配置文件和反射機制來對客戶端代碼進行改進,如在抽象工廠模式中可以將具體工廠類類名存儲在配置文件中,在適配器模式中可以將適配器類類名存儲在配置文件中,在策略模式中可以將具體策略類類名存儲在配置文件中等等。通過對代碼的改進,可以讓系統具有更好的擴展性和靈活性,更加滿足各種面向對象設計原則的要求。
總結
以上是生活随笔為你收集整理的设计模式之反射与配置文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 防重复请求处理的实践与总结
- 下一篇: jQuery选择器全集详解