• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Impossible is nothing  
              愛過知情重醉過知酒濃   花開花謝終是空   緣份不停留像春風來又走   女人如花花似夢
            公告
            日歷
            <2006年3月>
            2627281234
            567891011
            12131415161718
            19202122232425
            2627282930311
            2345678
            統計
            • 隨筆 - 8
            • 文章 - 91
            • 評論 - 16
            • 引用 - 0

            導航

            常用鏈接

            留言簿(4)

            隨筆分類(4)

            隨筆檔案(8)

            文章分類(77)

            文章檔案(91)

            相冊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

             

            軟件設計最大的敵人,就是應付需求不斷的變化。變化有時候是無窮盡的,于是項目開發就在反復的修改、更新中無限期地延遲交付的日期。變化如懸在頭頂的達摩克斯之劍,令許多軟件工程專家一籌莫展。正如無法找到解決軟件開發的“銀彈”,要徹底將變化扼殺在搖籃之中,看來也是不可能完成的任務。那么,積極地面對“變化”,方才是可取的態度。于是,極限編程(XP)的倡導者與布道者Kent Beck提出要“擁抱變化”,從軟件工程方法的角度,提出了應對“變化”的解決方案。而本文則試圖從軟件設計方法的角度,來探討如何在軟件設計過程中,解決未來可能的變化,其方法就是——封裝變化。

            設計模式是“封裝變化”方法的最佳闡釋。無論是創建型模式、結構型模式還是行為型模式,歸根結底都是尋找軟件中可能存在的“變化”,然后利用抽象的方式對這些變化進行封裝。由于抽象沒有具體的實現,就代表了一種無限的可能性,使得其擴展成為了可能。所以,我們在設計之初,除了要實現需求所設定的用例之外,還需要標定可能或已經存在的“變化”之處。封裝變化,最重要的一點就是發現變化,或者說是尋找變化。

            GOF對設計模式的分類,已經彰顯了“封裝變化”的內涵與精髓。創建型模式的目的就是封裝對象創建的變化。例如Factory Method模式和Abstract Factory模式,建立了專門的抽象的工廠類,以此來封裝未來對象的創建所引起的可能變化。而Builder模式則是對對象內部的創建進行封裝,由于細節對抽象的可替換性,使得將來面對對象內部創建方式的變化,可以靈活的進行擴展或替換。

            至于結構型模式,它關注的是對象之間組合的方式。本質上說,如果對象結構可能存在變化,主要在于其依賴關系的改變。當然對于結構型模式來說,處理變化的方式不僅僅是封裝與抽象那么簡單,還要合理地利用繼承與聚合的方法,靈活地表達對象之間的依賴關系。例如Decorator模式,描述的就是對象間可能存在的多種組合方式,這種組合方式是一種裝飾者與被裝飾者之間的關系,因此封裝這種組合方式,抽象出專門的裝飾對象顯然正是“封裝變化”的體現。同樣地,Bridge模式封裝的則是對象實現的依賴關系,而Composite模式所要解決的則是對象間存在的遞歸關系。

            行為型模式關注的是對象的行為。行為型模式需要做的是對變化的行為進行抽象,通過封裝以達到整個架構的可擴展性。例如策略模式,就是將可能存在變化的策略或算法抽象為一個獨立的接口或抽象類,以實現策略擴展的目的。Command模式、State模式、Vistor模式、Iterator模式概莫如是。或者封裝一個請求(Command模式),或者封裝一種狀態(State模式),或者封裝“訪問”的方式(Visitor模式),或者封裝“遍歷”算法(Iterator模式)。而這些所要封裝的行為,恰恰是軟件架構中最不穩定的部分,其擴展的可能性也最大。將這些行為封裝起來,利用抽象的特性,就提供了擴展的可能。

            利用設計模式,通過封裝變化的方法,可以最大限度的保證軟件的可擴展性。面對紛繁復雜的需求變化,雖然不可能完全解決因為變化帶來的可怕夢魘,然而,如能在設計之初預見某些變化,仍有可能在一定程度上避免未來存在的變化為軟件架構帶來的災難性傷害。從此點看,雖然沒有“銀彈”,但從軟件設計方法的角度來看,設計模式也是一枚不錯的“銅彈”了。

            考慮一個日志記錄工具。目前需要提供一個方便的日志API,使得客戶可以輕松地完成日志的記錄。該日志要求被記錄到指定的文本文件中,記錄的內容屬于字符串類型,其值由客戶提供。我們可以非常容易地定義一個日志對象:

            public class Log

            {

                   public void Write(string target, string log)

            {

                   //實現內容;

            }

            }

            當客戶需要調用日志的功能時,可以創建日志對象,完成日志的記錄:

            Log log = new Log();

            log.Write(“error.log”, “log”);

            然而隨著日志記錄的頻繁使用,有關日志的文件越來越多,日志的查詢與管理也變得越不方便。此時,客戶提出,需要改變日志的記錄方式,將日志內容寫入到指定的數據表中。顯然,如果仍然按照前面的設計,具有較大的局限性。

            現在我們回到設計之初,想象一下對于日志API的設計,需要考慮到這樣的變化嗎?這里存在兩種設計理念,即漸進的設計和計劃的設計。從本例來分析,要求設計者在設計初就考慮到日志記錄方式在未來的可能變化,并不容易。再者,如果在最開始就考慮全面的設計,會產生設計上的冗余。因此,采用計劃的設計固然具有一定的前瞻性,但一方面對設計者的要求過高,同時也會產生一些缺陷。那么,采用漸進的設計時,遇到需求變化時,利用重構的方法,改進現有的設計,又需要考慮未來的再一次變化嗎?這是一個見仁見智的問題。對于本例而言,我們完全可以直接修改Write()方法,接受一個類型判斷的參數,從而解決此問題。但這樣的設計,自然要擔負因為未來可能的再一次變化,而導致代碼大量修改的危險,例如,我們要求日志記錄到指定的Xml文件中。

            所以,變化是完全可能的。在時間和技術能力允許的情況下,我更傾向于將變化對設計帶來的影響降低到最低。此時,我們需要封裝變化。

            在封裝變化之前,我們需要弄清楚究竟是什么發生了變化?從需求看,是日志記錄的方式發生了變化。從這個概念分析,可能會導致兩種不同的結果。一種情形是,我們將日志記錄的方式視為一種行為,確切的說,是用戶的一種請求。另一種情形則從對象的角度來分析,我們將各種方式的日志看作不同的對象,它們調用接口相同的行為,區別僅在于創建的是不同的對象。前者需要我們封裝“用戶請求的變化”,而后者需要我們封裝“日志對象創建的變化”。

            封裝“用戶請求的變化”,在這里就是封裝日志記錄可能的變化。也就是說,我們需要把日志記錄行為抽象為一個單獨的接口,然后才分別定義不同的實現。如圖一所示:


            change01.jpg
            圖一:封裝日志記錄行為的變化

            如果熟悉設計模式,可以看到圖一所表示的結構正是Command模式的體現。由于我們對日志記錄行為進行了接口抽象,用戶就可以自由地擴展日志記錄的方式,只需要實現ILog接口即可。至于Log對象,則存在與ILog接口的弱依賴關系:

            public class Log

            {

                   private ILog log;

                   public Log(ILog log)

                   {

                          this.log = log;

                   }

                   public void Write(string target, string logValue)

                   {

                          log.Execute(target, logValue);

                   }

            }

            我們也可以通過封裝“日志對象創建的變化”實現日志API的可擴展性。在這種情況下,日志會根據記錄方式的不同,被定義為不同的對象。當我們需要記錄日志時,就創建相應的日志對象,然后調用該對象的Write()方法,實現日志的記錄。此時,可能會發生變化的是需要創建的日志對象,那么要封裝這種變化,就可以定義一個抽象的工廠類,專門負責日志對象的創建,如圖二所示:


            change02.jpg
            圖二:封裝日志對象創建的變化

            圖二是Factory Method模式的體現,由抽象類LogFactory專門負責Log對象的創建。如果用戶需要記錄相應的日志,例如要求日志記錄到數據庫,需要先創建具體的LogFactory對象:

            LogFactory factory = new DBLogFactory();

            當在應用程序中,需要記錄日志,那么再通過LogFactory對象來獲取新的Log對象:

            Log log = factory.Create();

            log.Write(“ErrorLog”, “log”);

            如果用戶需要改變日志記錄的方式為文本文件時,僅需要修改LogFactory對象的創建即可:

            LogFactory factory = new TxtFileLogFactory();

            為了更好地理解“封裝對象創建的變化”,我們再來看一個例子。假如,我們需要設計一個數據庫組件,它能夠訪問微軟的Sql Server數據庫。根據ADO.Net的知識,我們需要使用如下的對象:

            SqlConnection, SqlCommand, SqlDataAdapter等。

            如果僅就Sql Server而言,在訪問數據庫時,我們可以直接創建這些對象:

            SqlConnection connection = new SqlConnection(strConnection);

            SqlCommand command = new SqlCommand(connection);

            SqlDataAdapter adapter = new SqlDataAdapter();

            如果在一個數據庫組件中,充斥著如上的語句,顯然是不合理的。它充滿了僵化的壞味道,一旦要求支持其他數據庫時,原有的設計就需要徹底的修改,這為擴展帶來了困難。

            那么我們來思考一下,以上的設計應該做怎樣的修改?假定該數據庫組件要求或者將來要求支持多種數據庫,那么對于ConnectionCommandDataAdapter等對象而言,就不能具體化為Sql Server的對象。也就是說,我們需要為這些對象建立一個繼承的層次結構,為他們分別建立抽象的父類,或者接口。然后針對不同的數據庫,定義不同的具體類,這些具體類又都繼承或實現各自的父類,例如Connection對象:


            change03.jpg
            圖三:
            Connection對象的層次結構

            我為Connection對象抽象了一個統一的IConnection接口,而支持各種數據庫的Connection對象都實現了IConnection接口。同樣的,Command對象和DataAdapter對象也采用了相似的結構。現在,我們要創建對象的時候,可以利用多態的原理創建:

            IConnection connection = new SqlConnection(strConnection);

            從這個結構可以看到,根據訪問的數據庫的不同,對象的創建可能會發生變化。也就是說,我們需要設計的數據庫組件,以現在的結構來看,仍然存在無法應對對象創建發生變化的問題。利用“封裝變化”的原理,我們有必要把創建對象的責任單獨抽象出來,以進行有效地封裝。例如,如上的創建對象的代碼,就應該由專門的對象來負責。我們仍然可以建立一個專門的抽象工廠類DBFactory,并由它負責創建ConnectionCommandDataAdapter對象。至于實現該抽象類的具體類,則與目標對象的結構相同,根據數據庫類型的不同,定義不同的工廠類,類圖如圖四所示:


            change04.jpg
            圖四:
            DBFactory的類圖

            圖四是一個典型的Abstract Factory模式的體現。類DBFactory中的各個方法均為abstract方法,所以我們也可以用接口來代替該類的定義。繼承DBFactory類的各個具體類,則創建相對應的數據庫類型的對象。以SqlDBFactory類為例,創建各自對象的代碼如下:

            public class SqlDBFactory: DBFactory

            {

                   public override IConnection CreateConnection(string strConnection)

                   {

                          return new SqlConnection(strConnection);

                   }

                   public override ICommand CreateCommand(IConnection connection)

                   {

                          return new SqlCommand(connection);

                   }

                   public override IDataAdapter CreateDataAdapter()

                   {

                          return new SqlDataAdapter();

                   }

            }

            現在要創建訪問Sql Server數據庫的相關對象,就可以利用工廠類來獲得。首先,我們可以在程序的初始化部分創建工廠對象:

            DBFactory factory = new SqlDBFactory();

            然后利用該工廠對象創建相應的ConnectionCommand等對象:

            IConnection connection = factory.CreateConnection(strConnection);

            ICommand command = factory.CreateCommand(connection);

            由于我們利用了封裝變化的原理,建立了專門的工廠類,以封裝對象創建的變化。可以看到,當我們引入工廠類后,ConnectionCommand等對象的創建語句中,已經成功地消除了其與具體的數據庫類型相依賴的關系。在如上的代碼中,并未出現Sql之類的具體類型,如SqlConnectionSqlCommand等。也就是說,現在創建對象的方式是完全抽象的,是與具體實現無關的。無論是訪問何種數據庫,都與這幾行代碼無關。至于涉及到的數據庫類型的變化,則全部抽象到DBFactory抽象類中了。需要更改訪問數據庫的類型,我們也只需要修改創建工廠對象的那一行代碼,例如將Sql Server類型修改為Oracle類型:

            DBFactory factory = new OracleDBFactory();

            很顯然,這樣的方式提高了數據庫組件的可擴展性。我們將可能發生變化的部分封裝起來,放到程序固定的部分,例如初始化部分,或者作為全局變量,更可以將這些可能發生變化的地方,放到配置文件中,通過讀取配置文件的值,創建相對應的對象。如此一來,不需要修改代碼,也不需要重新編譯,僅僅是修改xml文件,就能實現數據庫類型的改變。例如,我們創建如下的配置文件:

            <appSettings>

                   <add key=”db” value=”SqlDBFactory”/>

            </appSettings>

            創建工廠對象的代碼則相應修改如下:

            string factoryName = ConfigurationSettings.AppSettings[“db”].ToString();

            //DBLib為數據庫組件的程序集:

            DBFactory factory = (DBFactory)Activator.CreateInstance(“DBLib”,factoryName).Unwrap();

                   當我們需要將訪問的數據庫類型修改為Oracle數據庫時,只需要將配置文件中的Value值修改為“OracleDBFactory”即可。這種結構具有很好的可擴展性,較好地解決了未來可能發生的需求變化所帶來的問題。

            posted on 2006-02-23 14:52 笑笑生 閱讀(369) 評論(0)  編輯 收藏 引用 所屬分類: 軟件工程
             
            Copyright © 笑笑生 Powered by: 博客園 模板提供:滬江博客
            日本精品久久久久久久久免费| 久久久久亚洲AV无码麻豆| 久久综合香蕉国产蜜臀AV| 亚洲国产精品无码久久SM| 久久国产精品99精品国产| 伊人久久综合热线大杳蕉下载| 久久久久亚洲精品中文字幕| 精品国产日韩久久亚洲| 亚洲综合伊人久久大杳蕉| 久久99精品久久久久久动态图| 国产精品美女久久久久网| 久久久久九九精品影院| 亚洲狠狠婷婷综合久久久久| 久久这里只精品国产99热| 噜噜噜色噜噜噜久久| 久久婷婷综合中文字幕| 久久精品国产久精国产果冻传媒| 久久国产精品99精品国产987| 久久久久黑人强伦姧人妻| 午夜精品久久久久久99热| 青草影院天堂男人久久| 精品久久久久成人码免费动漫 | 97久久精品午夜一区二区| 久久久久久久久久免免费精品| 亚洲av日韩精品久久久久久a| 国产无套内射久久久国产| 久久亚洲精品无码AV红樱桃| 久久中文字幕视频、最近更新| 久久综合88熟人妻| 久久毛片一区二区| 久久精品无码一区二区三区日韩| 久久精品a亚洲国产v高清不卡| 四虎国产精品免费久久| 久久精品无码一区二区日韩AV | 亚洲午夜精品久久久久久人妖| 久久频这里精品99香蕉久| 久久久久久一区国产精品| 精品久久久久久久久久中文字幕| 久久se精品一区二区| 久久综合综合久久狠狠狠97色88| 久久精品亚洲一区二区三区浴池|