<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Feng.Li's Java See

    抓緊時(shí)間,大步向前。
    隨筆 - 95, 文章 - 4, 評(píng)論 - 58, 引用 - 0
    數(shù)據(jù)加載中……

    面向?qū)ο笤O(shè)計(jì)(OOD)思想(C#)

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




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


    只有注冊用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 亚洲国产夜色在线观看| 一级做a爰片久久毛片免费陪| 午夜一级毛片免费视频| 四虎精品免费永久免费视频| 亚洲AV无码成人精品区天堂| 成人啪精品视频免费网站| 中文毛片无遮挡高清免费| 亚洲一区中文字幕在线电影网| 免费人成无码大片在线观看| 一级毛片免费观看| 老外毛片免费视频播放| 蜜芽亚洲av无码精品色午夜| 国产免费久久精品| 最近在线2018视频免费观看| 老司机午夜免费视频| 91亚洲国产成人久久精品网址| 亚洲天堂中文字幕在线| 成人免费午夜视频| 亚洲免费精彩视频在线观看| 国产AV无码专区亚洲AV琪琪| 亚洲网址在线观看| 日韩亚洲变态另类中文| 日韩成人在线免费视频| 88xx成人永久免费观看| 精品国产福利尤物免费| 亚洲av永久无码天堂网| 亚洲视屏在线观看| 亚洲精品无码成人AAA片| 免费成人黄色大片| 成人黄页网站免费观看大全| 毛片无码免费无码播放| 久久免费香蕉视频| 免费看一级高潮毛片| 亚洲AV无码一区二区乱子仑 | 亚洲网红精品大秀在线观看| 亚洲国产小视频精品久久久三级 | 男人和女人高潮免费网站| 亚洲人成影院77777| 精品亚洲aⅴ在线观看| 亚洲精品无码久久久久| 久久伊人亚洲AV无码网站|