設計模式之反射與設定檔

來源:互聯網
上載者:User

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

      (1)用戶端針對抽象層編程,用戶端代碼中不能出現任何具體類類名,即用戶端不直接執行個體化對象;

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

      (3)通過DOM(Document Object Model,文件物件模型)、SAX(Simple
APIfor XML)等XML解析技術擷取儲存在設定檔中的類名;

      (4)在用戶端代碼中通過反射(Reflection)機制根據類名建立對象,用反射所建立的對象替換父類對象的引用,程式運行時,將調用子類方法來實現業務功能;

      (5)如果需要擴充功能,只需增加一個新的子類繼承抽象父類,再修改設定檔,重新運行程式即可;如果需要替換功能,只需用另一個子類類名替換儲存在設定檔中的原有子類類名即可。無論是擴充還是替換都無須修改既有類庫和用戶端原始碼,完全符合開閉原則。

      下面通過Factory 方法模式來說明如何使用設定檔和反射機制:

【執行個體說明】:寶馬(BMW)工廠可以生產寶馬轎車,平治(Benz)工廠可以生產平治轎車,使用Factory 方法模式來設計該情境,所得類圖1所示:

圖1 Factory 方法模式執行個體類圖

      
在圖1中,CarFactory是抽象工廠,聲明了Factory 方法produceCar(),在其子類中實現了該方法,用於返回具體的產品。在用戶端代碼中將出現如下代碼:

CarFactory cf;

Car car;

cf  = new BMWFactory();  //建立具體工廠

car = cf.produceCar(); //使用Factory 方法建立產品對象

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類的靜態Factory 方法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#來反射產生對象的過程相對較為複雜,感興趣的讀者可以參考其他相關資料,在此不予擴充。

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

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.