国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > 綜合 > 正文

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

2024-07-21 02:30:17
字體:
來源:轉載
供稿:網友
國內最大的酷站演示中心!

       有了翅膀才能飛,欠缺靈活的代碼就象凍壞了翅膀的鳥兒。不能飛翔,就少了幾許靈動的氣韻。我們需要給代碼帶去溫暖的陽光, 讓僵冷的翅膀重新飛起來。結合實例,通過應用oop、設計模式和重構,你會看到代碼是怎樣一步一步復活的。 為了更好的理解設計思想,實例盡可能簡單化。但隨著需求的增加,程序將越來越復雜。此時就有修改設計的必要, 重構和設計模式就可以派上用場了。最后當設計漸趨完美后,你會發現,即使需求不斷增加,你也可以神清氣閑,不用為代碼設計而煩惱了。

      假定我們要設計一個媒體播放器。該媒體播放器目前只支持音頻文件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,你會不斷地增加相應地播放方法, 然后讓switch子句越來越長,直至達到你視線看不到的地步。

       好吧,我們先來體驗對象的精神。根據oop的思想,我們應該把mp3和wav看作是一個獨立的對象。那么是這樣嗎?

public class mp3
{
   public void play()
   {
       messagebox.show("play the mp3 file.");
   }
}

       好樣的,你已經知道怎么建立對象了。更可喜的是,你在不知不覺中應用了重構的方法,把原來那個垃圾設計中的方法名字改為了 統一的play()方法。你在后面的設計中,會發現這樣改名是多么的關鍵!但似乎你并沒有擊中要害, 以現在的方式去更改mediaplayer的代碼,實質并沒有多大的變化。
       
既然mp3和wav都屬于音頻文件,他們都具有音頻文件的共性,為什么不為它們建立一個共同的父類呢?

public class audiomedia
{
   public void play()
   {
       messagebox.show("play the audiomedia file.");
   }
}

        現在我們引入了繼承的思想,oop也算是象模象樣了。得意之余,還是認真分析現實世界吧。其實在現實生活中,我們播放的只會是某種具體類型的音頻文件,因此這個audiomedia類并沒有實際使用的情況。對應在設計中,就是:這個類永遠不會被實例化。所以,還得動一下手術,將其改為抽象類。好了,現在的代碼有點oop的感覺了:

        看看現在的設計,即滿足了類之間的層次關系,同時又保證了類的最小化原則,更利于擴展(到這里,你會發現play方法名改得多有必要)。 即使你現在又增加了對wma文件的播放,只需要設計wma類,并繼承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#里只支持類的單繼承)。雖然視頻和音頻格式不同,別忘了,他們都是媒體中的一種,很多時候,他們有許多相似的功能,比如播放。根據接口的定義,你完全可以將相同功能的一系列對象實現同一個接口:

public interface imedia
{
   void play();
}

public abstract class audiomedia:imedia
{
   public abstract void play();
}

public abstract class videomedia:imedia
{
   public abstract void play();
}

       再更改一下mediaplayer的設計就ok了:

public class mediaplayer

   public void play(imedia media)
   {     
       media.play();
   }
}

        現在可以總結一下,從mediaplayer類的演變,我們可以得出這樣一個結論:在調用類對象的屬性和方法時,盡量避免將具體類對象作為傳遞參數,而應傳遞其抽象對象,更好地是傳遞接口,將實際的調用和具體對象完全剝離開,這樣可以提高代碼的靈活性。

        不過,事情并沒有完。雖然一切看起來都很完美了,但我們忽略了這個事實,就是忘記了mediaplayer的調用者。還記得文章最開始的switch語句嗎?看起來我們已經非常漂亮地除掉了這個煩惱。事實上,我在這里玩了一個詭計,將switch語句延后了。雖然在mediaplayer中,代碼顯得干凈利落,其實煩惱只不過是轉嫁到了mediaplayer的調用者那里。
例如,在主程序界面中: 
 
public void btnplay_click(object sender,eventargs e)
{
    switch (cbbmediatype.selectitem.tostring().tolower())
    {
        imedia media;
        case ("mp3"):
             media = new mp3();
             break;
        case ("wav"):
             media = new wav();
             break;  
        //其它類型略;
    }
    mediaplayer player = new mediaplayer();
    player.play(media);
}

       用戶通過選擇cbbmediatype組合框的選項,決定播放哪一種文件,然后單擊play按鈕執行。

       現在該設計模式粉墨登場了,這種根據不同情況創建不同類型的方式,工廠模式是最拿手的。先看看我們的工廠需要生產哪些產品呢?雖然這里有兩種不同類型的媒體audiomedia和videomedia(以后可能更多),但它們同時又都實現imedia接口,
所以我們可以將其視為一種產品,用工廠方法模式就可以了。首先是工廠接口:

public interface imediafactory
{
   imedia createmedia();
}

       然后為每種媒體文件對象搭建一個工廠,并統一實現工廠接口:

public class mp3mediafactory:imediafactory
{
   public imedia createmedia()
   {
       return new mp3();
   }
}
public class rmmediafactory:imediafactory
{
   public imedia createmedia()
   {
       return new rm();
   }
}
//其它工廠略;

寫到這里,也許有人會問,為什么不直接給audiomedia和videomedia類搭建工廠呢?很簡單,因為在audiomedia和videomedia中,分別還有不同的類型派生,如果為它們搭建工廠,則在createmedia()方法中,仍然要使用switch語句。而且既然這兩個類都實現了imedia接口,可以認為是一種類型,為什么還要那么麻煩去請動抽象工廠模式,來生成兩類產品呢? 可能還會有人問,即使你使用這種方式,那么在判斷具體創建哪個工廠的時候,不是也要用到switch語句嗎?我承認這種看法是對的。不過使用工廠模式,其直接好處并非是要解決switch語句的難題,而是要延遲對象的生成,以保證的代碼的靈活性。當然,我還有最后一招殺手锏沒有使出來,到后面你會發現,switch語句其實會完全消失。

還有一個問題,就是真的有必要實現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 mp3mediafactory();
             break;
        case ("wav"):
             factory = new wavmediafactory();
             break;  
        //其他類型略;
    }
    mediaplayer player = new mediaplayer();
    player.play(factory.createmedia());
}

寫到這里,我們再回過頭來看mediaplayer類。這個類中,實現了play方法,并根據傳遞的參數,調用相應媒體文件的play方法。在沒有工廠對象的時候,看起來這個類對象運行得很好。如果是作為一個類庫或組件設計者來看,他提供了這樣一個接口,供主界面程序員調用。然而在引入工廠模式后,在里面使用mediaplayer類已經多余了。所以,我們要記住的是,重構并不僅僅是往原來的代碼添加新的內容。當我們發現一些不必要的設計時,還需要果斷地刪掉這些冗余代碼。
public void btnplay_click(object sender,eventargs e)
{
imediafactory factory = null;
    switch (cbbmediatype.selectitem.tostring().tolower())
    {       
        case ("mp3"):
             factory = new mp3mediafactory();
             break;
        case ("wav"):
             factory = new wavmediafactory();
             break;  
        //其他類型略;
    }
    imedia media = factory.createmedia();
    media.play();
}

如果你在最開始沒有體會到imedia接口的好處,在這里你應該已經明白了。我們在工廠中用到了該接口;而在主程序中,仍然要使用該接口。使用接口有什么好處?那就是你的主程序可以在沒有具體業務類的時候,同樣可以編譯通過。因此,即使你增加了新的業務,你的主程序是不用改動的。

不過,現在看起來,這個不用改動主程序的理想,依然沒有完成??吹搅藛??在btnplay_click()中,依然用new創建了一些具體類的實例。如果沒有完全和具體類分開,一旦更改了具體類的業務,例如增加了新的工廠類,仍然需要改變主程序,何況討厭的switch語句仍然存在,它好像是翅膀上滋生的毒瘤,提示我們,雖然翅膀已經從僵冷的世界里復活,但這雙翅膀還是有病的,并不能正常地飛翔。

是使用配置文件的時候了。我們可以把每種媒體文件類類型的相應信息放在配置文件中,然后根據配置文件來選擇創建具體的對象。
并且,這種創建對象的方法將使用反射來完成。首先,創建配置文件:

<appsettings>
  <add key="mp3" value="wingproject.mp3factory" />
  <add key="wav" value="wingproject.wavfactory" />
  <add key="rm" value="wingproject.rmfactory" />
  <add key="mpeg" value="wingproject.mpegfactory" />
</appsettings>

然后,在主程序界面的form_load事件中,讀取配置文件的所有key值,填充cbbmediatype組合框控件:
public void form_load(object sender, eventargs e)
{
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類,并實現imedia接口,同時繼承videomedia類。另外在工廠業務中創建avimediafactory類,并實現imediafactory接口。假設這個新的工廠類型為wingproject.avifactory,則在配置文件中添加如下一行:
<add key="avi" value="wingproject.avifactory" />。
而主程序呢?根本不需要做任何改變,甚至不用重新編譯,這雙翅膀照樣可以自如地飛行!

 

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();
   }
}

 

public class wav
{
   public void play()
   {
       messagebox.show("play the wav file.");
   }
}

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 正阳县| 宣汉县| 台山市| 尚志市| 和田县| 胶南市| 廉江市| 新河县| 武汉市| 卓资县| 胶州市| 灌南县| 绍兴县| 会理县| 门头沟区| 木兰县| 穆棱市| 北辰区| 南和县| 新化县| 旅游| 阜康市| 莱芜市| 池州市| 澄江县| 泸西县| 长兴县| 桂平市| 巧家县| 申扎县| 广州市| 山阴县| 年辖:市辖区| 武定县| 金平| 翼城县| 黔南| 黔西| 兴山县| 嘉峪关市| 翁源县|