設計模式之反射與配置檔案

Liuwei-Sunny發表於2012-04-05

        為了滿足“開閉原則”,大部分設計模式都引入了抽象層,如工廠方法模式、抽象工廠模式、介面卡模式、橋接模式、命令模式、策略模式等等。客戶端程式碼針對抽象層程式設計,而在程式執行的時候再指定其子類,根據“里氏代換原則”和麵向物件的多型性,子類物件在執行時將覆蓋父類物件。如果需要對系統進行擴充套件或修改,只需修改子類類名即可。在具體實現時,通過引入配置檔案可以使得使用者在不修改任何客戶端程式碼的前提下增加或替換子類,其基本實現過程如下:

      (1)客戶端針對抽象層程式設計,客戶端程式碼中不能出現任何具體類類名,即客戶端不直接例項化物件;

      (2)引入純文字格式的配置檔案,通常是XML檔案,將具體類類名儲存在配置檔案中;

      (3)通過DOMDocument Object Model,文件物件模型)、SAXSimple 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)是指在程式執行時獲取已知名稱的類或已有物件的相關資訊的一種機制,包括類的方法、屬性、父類等資訊,還包 括例項的建立和例項型別的判斷等。在反射中使用最多的類是ClassClass類的例項表示正在執行的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的程式集中封裝了型別後設資料資訊,因此可以先通過AssemblyLoad("程式集名稱")方法載入一個程式集,再通過其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++中實現類似JavaC#來反射生成物件的過程相對較為複雜,感興趣的讀者可以參考其他相關資料,在此不予擴充套件。

      在引入配置檔案和反射機制後,需要更換或增加新的具體類將變得很簡單,只需增加新的具體類並修改配置檔案即可,無須對現有類庫和客戶端程式碼進行任何修改,完全符合開閉原則。在很多設計模式中都可以通過引入配置檔案和反射機制來對客戶端程式碼進行改進,如在抽象工廠模式中可以將具體工廠類類名儲存在配置檔案中,在介面卡模式中可以將介面卡類類名儲存在配置檔案中,在策略模式中可以將具體策略類類名儲存在配置檔案中等等。通過對程式碼的改進,可以讓系統具有更好的擴充套件性和靈活性,更加滿足各種物件導向設計原則的要求。

【作者:劉偉 http://blog.csdn.net/lovelion

相關文章