Feng.Li's Java See

          抓緊時間,大步向前。
          隨筆 - 95, 文章 - 4, 評論 - 58, 引用 - 0
          數據加載中……

          面向對象設計(OOD)思想(C#)

          h
          面向對象設計(OOD)思想(C#
           
          有了思想才能飛翔,缺乏靈活就象少了輪子的汽車,難以飛奔。為了更好的理解設計思想,結合一個盡可能簡潔的實例來說明OOD、設計模式及重構。通過下面的代碼,詳細地闡述面向對象設計思想。 
          一、傳統過程化設計思想
          假定我們要設計一個媒體播放器(只從軟件設計的角度,不涉及硬件)。該媒體播放器目前只支持音頻文件mp3和wav。按照結構化設計思想,設計出來的播放器的代碼如下:
          public class MediaPlayer 
          {   
             private void PlayMp3() 
             { 
                MessageBox.Show("Play the mp3 file."); 
             } 
           
             private void PlayWav() 
             { 
                MessageBox.Show("Play the wav file."); 
             } 
           
             public void Play(string audioType) 
             {      
                switch (audioType.ToLower()) 
                { 
                    case ("mp3"): 
                       PlayMp3(); 
                       break; 
                    case ("wav"): 
                       PlayWav(); 
                       break;             
                }      
             } 
          從傳統的過程化設計思想來看,這是一段既實用又簡潔的代碼。
          如果,客戶又提出新的要求:要播放器不僅僅播放mp3和wav文件,還要播放其他音頻文件如wma、mp4等,為此我們要不斷地增加相應地播放方法和修改條件語句,直止條件語句足夠長。
          如果,客戶感到這個媒體播放器功能太少了,只能聞其聲,不能見其人,太單一。如果在聽著優美音樂的同時又能看到歌唱者瀟灑、英俊的舞姿那就更好了。從代碼設計的角度看,他們希望媒體播放器支持視頻文件了。也許你會想,不會再增加視頻這方面的代碼,可以,在增加視頻媒體的播放方法,在修改條件判斷語句,如果還有其他,還可以同樣地增加、修改。到此你也許會提出,要是不修改或很少修改原來的代碼就能增添其他功能該多好啊!
          這樣看,原來的軟件設計結構似乎有點問題。事實上,隨著功能的不斷增加,你越來越發現這個設計非常的糟糕,因為它根本沒有為未來的需求變更提供最起碼的擴展。為了應接不暇的變更需求,你不得不不厭其煩地修改原來的代碼,使其適應需求變化,甚至在修改代碼時,由于過多的代碼依賴關系弄得人焦頭爛額,直止一塌糊涂。
          二、面向對象設計思想
          還是以設計一個媒體播放器為例,設計要求相同。不訪我們換個設計思路利用面向對象設計思想(OOD)來做做看如何!
          根據OOD的思想,我們應該把mp3和wav分別看作是兩個獨立的對象。代碼設計如下:
          public class MP3 
             public void Play() 
             { 
                 MessageBox.Show("Play the mp3 file."); 
             } 
           
          public class WAV 
             public void Play() 
             { 
                 MessageBox.Show("Play the wav file."); 
             } 
           
          Public class MediaPlayer
          {
                switch (audioType.ToLower()) 
                { 
                    case ("mp3"): 
                                MP3 m = new MP3();
                       m.Play(); 
                       break; 
                    case ("wav"): 
                       WAV w = new WAV();
          w.Play(); 
                       break;             
                }
          }     
                 現在我們重構代碼,建立統一的Play()方法,(在后面的設計中,你會發現這樣改名是多么的重要!)更改媒體播放類MediaPlayer的代碼。如果這樣的設計代碼,實質上沒有多大的變化,只是對原來過程化設計思想的一種替代,并沒有擊中要害,亦然沒有靈活性、可擴展性。
                 2.1單向分派技術的應用(在這里用類的多態來實現的)
          我們不訪這樣設想:既然mp3和wav都屬于音頻文件,都具有音頻文件的共性,應該建立一個共同的AudioMedia父類。 
          public class AudioMedia 
             public void Play() 
             { 
                 MessageBox.Show("Play the AudioMedia file."); 
             } 
          現在引入繼承思想,OOD就有點雛形了(不是說有了繼承就有了OOD思想,這里只是從繼承的角度談一談OOD思想,當然從其他角度如合成、聚合等角度也能很好地體現OOD思想)。
          其實在現實生活中,我們的播放器播放的只能是某種具體類型的音頻文件如mp3,因此這個AudioMedia類只能是音頻媒體的一個抽象化概念,并沒有實際的使用情況。對應在OOD設計中,既這個類永遠不會被實例化。為此我們應將其改為抽象類,如下:
          public abstract class AudioMedia 
             public abstract void Play(); 
           
          public class MP3:AudioMedia 
             public override void Play() 
             { 
                 MessageBox.Show("Play the mp3 file."); 
             } 
           
          public class WAV:AudioMedia 
             public override void Play() 
             { 
                 MessageBox.Show("Play the wav file."); 
             } 
           
          public class MediaPlayer 
          {  
                 //根據需要完成任務的單向分派
             public void Play(AudioMedia media) 
             {      
                 media.Play(); 
             } 
          到此,我們通過單向分派技術使OOD思想得到進一步的體現。現在的設計,即滿足了類之間的層次關系,又保證了類的最小化原則,同時又體現了面向對象設計原則(開—閉原則、里氏代換原則)更利于擴展。(止此,你會發現play方法名的更改是多么必要)。 
          如果現在又增加了對WMA、MP4等音頻文件的播放,只需要設計WMA類,MP4類,并繼承AudioMedia,在相應的子類中重寫Play方法就可以了,MediaPlayer類對象的Play方法根本不用任何改變。 
          如果讓媒體播放器能夠支持視頻文件,必須另外設計視頻媒體的類。因視頻文件和音頻文件有很多不同的地方,不可能讓視頻繼承音頻。假設我們播放器支持RM和MPEG格式的視頻。視頻類代碼如下: 
          public abstract class VideoMedia 
             public abstract void Play(); 
           
          public class RM:VideoMedia 
             public override void Play() 
             { 
                 MessageBox.Show("Play the rm file."); 
             } 
           
          public class MPEG:VideoMedia 
             public override void Play() 
             { 
                 MessageBox.Show("Play the mpeg file."); 
             } 
           
          這樣設計還是有點糟糕,這樣就無法實用原有的MediaPlayer類了。因為你要播放的視頻RM文件并不是音頻媒體AudioMedia的子類。
                 不過,我們可以這樣想,無論音頻媒體還是視頻媒體都是媒體,有很多相似的功能,如播放、暫停、停止等,為此我們把“媒體”這個概念抽象出來做為一個接口。(雖然也可以用抽象類,但在C#里只支持類的單繼承,不過c#支持接口的多繼承)。根據接口的定義,你完全可以將相同功能的一系列對象實現同一個接口。讓音頻媒體類及視頻媒體類都繼承媒體這個接口。代碼如下: 
           
          public interface IMedia 
             void Play(); 
           
          public abstract class AudioMedia:IMedia 
             public abstract void Play(); 
           
          public abstract class VideoMedia:IMedia 
             public abstract void Play(); 
           
          這樣再更改MediaPlayer類的代碼: 
          public class MediaPlayer 
          {  
             public void Play(IMedia media) 
             {      
                 media.Play(); 
             }  
                 現在看來,程序是不是有很大的靈活性和可擴展性了。
          總結一下,從MediaPlayer類的演變,我們可以得出這樣一個結論:在調用類對象的屬性和方法時,盡量避免將具體類對象作為傳遞參數,而應傳遞其抽象對象,更好地是傳遞接口,將實際的調用和具體對象完全剝離開,這樣可以很好地體現了軟件工程的靈活性、擴展性。 
                 現在看起來似乎很完美了,但我們忽略了MediaPlayer的調用者這個事實。仍然需要條件語句來實現。例如,在客戶端程序代碼中,用戶通過選擇cbbMediaType組合框的選項,決定播放音頻媒體還是視頻媒體,然后單擊Play按鈕執行。 
          Public void BtnPlay_Click(object sender,EventArgs e) 
              IMedia media = null;
              switch (cbbMediaType.SelectItem.ToString().ToLower()) 
              { 
                  case ("mp3"): 
                       media = new MP3(); 
                       break; 
                                //其它類型略;
                  case ("rm"): 
                       media = new RM(); 
                       break;   
                  //其它類型略; 
              } 
              MediaPlayer player = new MediaPlayer(); 
              player.Play(media); 
           
          2.2設計模式、條件外置及反射技術的應用
          隨著需求的增加,程序將會越來越復雜。此時就應調整設計思想,充分考慮到代碼的重構和設計模式的應用。最后當設計漸趨完美后,你會發現,即使需求不斷增加,你也可以神清氣爽,不用為代碼設計而煩惱了。 
          為了實現軟件工程的三個主要目標:重用性、靈活性和擴展性。我們不訪用設計模式、條件外置及反射來實現。
          使用工廠模式,能夠很好地根據需要,調用不同的對象(即動態調用),保證了代碼的靈活性。
          雖然這里有兩種不同類型的媒體AudioMedia和VideoMedia(以后可能更多),但它們同時又都實現IMedia接口,所以我們可以將其視為一種產品。媒體工廠接口如下: 
          public interface IMediaFactory 
             IMedia CreateMedia(); 
           
          然后為具體的媒體文件對象搭建工廠,并統一實現媒體工廠接口: 
          public class MP3Factory:IMediaFactory 
             public IMedia CreateMedia() 
             { 
                 return new MP3(); 
             } 
          //其它工廠略;
           
          public class RMFactory:IMediaFactory 
             public IMedia CreateMedia() 
             { 
                 return new RM(); 
             } 
          //其它工廠略; 
           
          寫到這里,也許有人會問,為什么不直接給AudioMedia和VideoMedia類搭建工廠呢?很簡單,因為在AudioMedia和VideoMedia中,分別還有不同的類型派生,如果為它們搭建工廠,則在CreateMedia()方法中,仍然要使用條件判斷語句,代碼缺乏靈活性,不利擴展。
          還有一個問題,就是真的有必要實現AudioMedia和VideoMedia兩個抽象類嗎?讓其子類直接實現接口不是更簡單?對于本文提到的需求,是能實現的。但不排除AudioMedia和VideoMedia它們還會存在其他區別,如音頻文件還需給聲卡提供接口,而視頻文件還需給顯卡提供接口。如果讓MP3、WAV、RM、MPEG直接實現IMedia接口,而不通過AudioMedia和VideoMedia,在滿足其它需求的設計上也是不合理的。現在客戶端程序代碼發生了稍許的改變: 
          Public void BtnPlay_Click(object sender,EventArgs e) 
          IMediaFactory factory = null; 
              switch (cbbMediaType.SelectItem.ToString().ToLower()) 
                 //音頻媒體
                  case ("mp3"): 
                       factory = new MP3Factory(); 
                       break; 
                  //視頻媒體
          case ("rm"): 
                       factory = new RMFactory(); 
                       break;   
                  //其他類型略; 
              } 
              MediaPlayer player = new MediaPlayer(); 
              player.Play(factory.CreateMedia()); 
           
          到這里,我們再回過頭來看MediaPlayer類。這個類中通過單向分派,根據傳遞參數的不同,分別實現了不同對象的Play方法。在不用工廠模式時,這個類對象會運行得很好。作為一個類庫或組件設計者來看,他提供了一個不錯的接口,供客戶端程序調用。
          利用工廠模式后,現在看來MediaPlayer類已經多余。所以,我們要記住的是,重構并不僅僅是往原來的代碼添加新的內容。當我們發現一些不必要的設計時,還需要果斷地刪掉這些冗余代碼。修改后的代碼如下: 
          Public void BtnPlay_Click(object sender,EventArgs e) 
          IMediaFactory factory = null; 
              switch (cbbMediaType.SelectItem.ToString().ToLower()) 
              {        
                  case ("mp3"): 
                       factory = new MP3Factory(); 
                       break; 
                        //其他類型略;
                  case ("rm"): 
                       factory = new RMFactory(); 
                       break;   
                  //其他類型略; 
              } 
              IMedia media = factory.CreateMedia(); 
              media.Play(); 
           
          如果你在最開始沒有體會到IMedia接口的好處,在這里你應該已經明白了。我們在工廠模式中用到了該接口;而在客戶端程序中,仍然要使用該接口。使用接口有什么好處?那就是你的主程序可以在沒有具體業務類的時候,同樣可以編譯通過。因此,即使你增加了新的業務,你的客戶端程序是不用改動的。 
          不過,這樣寫客戶端代碼還是不夠理想的,依然不夠靈活,在判斷具體創建哪個工廠的時候,仍需條件判斷。現在看來,如果執行者沒有完全和具體類分開,一旦更改了具體類的業務,例如增加了新的工廠類,仍然需要更改客戶端程序代碼。
          我們可以通過反射技術、條件外置很好地做到客戶端的靈活性。
          條件外置來實現,即通過應用程序的配置文件來實現。我們可以把每種媒體文件類的類型信息放在配置文件中,然后根據配置文件來選擇創建具體的對象。并且,這種創建對象的方法將使用反射技術來完成。首先,創建配置文件: 
           
          <appSettings> 
           <add key="mp3" value="MediaLibrary.MP3Factory" /> 
           <add key="wav" value=" MediaLibrary.WAVFactory" /> 
           <add key="rm" value=" MediaLibrary.RMFactory" /> 
           <add key="mpeg" value=" MediaLibrary.MPEGFactory" /> 
          </appSettings> 
           
          然后,在客戶端程序代碼中,自定義一個初始化方法如:InitMediaType(),讀取配置文件的所有key值,填充cbbMediaType組合框控件中: 
          private void InitMediaType() 
          cbbMediaType.Items.Clear(); 
          foreach (string key in ConfigurationSettings.AppSettings.AllKeys) 
                      cbbMediaType.Item.Add(key); 
          cbbMediaType.SelectedIndex = 0; 
           
          最后,更改客戶端程序的Play按鈕單擊事件: 
          Public void BtnPlay_Click(object sender,EventArgs e) 
          string mediaType = cbbMediaType.SelectItem.ToString().ToLower(); 
          string factoryDllName = ConfigurationSettings.AppSettings[mediaType].ToString(); 
          //MediaLibray為引用的媒體文件及工廠的程序集; 
          IMediaFactory factory = (IMediaFactory)Activator.CreateInstance(“MediaLibrary”,
          factoryDllName).Unwrap();
          IMedia media = factory.CreateMedia(); 
          media.Play(); 
           
          這樣可以很好地體現了軟件工程的三個主要目標:重用性、靈活性和擴展性。
          設想一下,如果我們要增加某種媒體文件的播放功能,如AVI文件。那么,我們只需要在原來的業務程序集中創建AVI類,繼承于VideoMedia類。另外在工廠業務中創建AVIFactory類,并實現IMediaFactory接口。假設這個新的工廠類型為MediaLiabrary.AVIFactory,則在配置文件中添加如下一行: 
          <add key="AVI" value="MediaLiabrary.AVIFactory" />。 
          而客戶端程序呢?根本不需要做任何改變,甚至不用重新編譯,程序就能自如地運行!
          另:本文發布后有熱心的朋友提出,如果提供UML圖,那就更完美了,謝謝這位朋友的提醒。UML圖現補充如下




          posted on 2007-08-16 19:28 小鋒 閱讀(201) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 海宁市| 长泰县| 宜丰县| 勃利县| 合肥市| 广西| 宣恩县| 徐闻县| 焦作市| 封丘县| 长宁区| 宝坻区| 勐海县| 达孜县| 庐江县| 昂仁县| 涡阳县| 金堂县| 宁陵县| 汾西县| 扶沟县| 眉山市| 来安县| 双桥区| 波密县| 铜陵市| 荥阳市| 静宁县| 山西省| 丹阳市| 尉氏县| 南阳市| 诏安县| 九江市| 长宁区| 邢台县| 军事| 晋宁县| 东莞市| 高安市| 库伦旗|