周玉鳳
[摘 要]:2005年初,筆者在北京參加了北大青鳥教師培訓班,學習的內容是C#。之后,經過幾年的教學實踐,筆者對面向對象程序設計有了更為深廣的理解,本文筆者就談談自己的認識與體會,以與中職計算機專業學生朋友交流。
[關鍵詞]:中職生 面向對象程序設計 C#
首先,筆者談幾點理解面向對象程序設計的要點:
封裝性:數據和行為封裝在一個自定義的數據類型里面,其作用:(1)把松散的方法聚合到數據類型里面。(2)隱藏數據類型內部的數據定義。
繼承性:把多個數據類型按照一定的方法進行抽象,把共有的屬性放的一個高層的數據類型里面,然后底層的多個類型可以共享這個高層類型的數據和方法。作用有兩個:(1)簡化數據類型的定義,減少重復定義。(2)使組織結構清晰化,這和現實世界的分門別類具有異曲同工之妙。
多態性:一個方法,可以根據其操縱的具體類型的對象的不同(抽象類型相同),可以有不同的表現。其作用:(1)進一步簡化方法的定義。(2)是多種不同的方法具有統一的接口,方便調用。
接下來,我們結合實例說說設計模式。
為了更好地理解設計思想,實例盡可能簡單化。但隨著需求的增加,程序將越來越復雜。此時,就有修改設計的必要,重構和設計模式就可以派上用場了。最后當設計漸趨完美后,你會發現,即使需求不斷增加,你也可以神清氣閑,不用為代碼設計而煩惱了。
假定我們要設計一個媒體播放器。該媒體播放器目前只支持音頻文件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子句越來越長,直至達到你視線看不到的地步。
我們來體驗面向對象的思想。把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.");
}
}
統一的Play()方法。在后面的設計中,會發現這樣改名是很關鍵的!
但以現在的方式去更改MediaPlayer的代碼,實質并沒有多大的變化。
既然mp3和wav都屬于音頻文件,他們都具有音頻文件的共性,我們就應該建立一個共同的父類。
public class AudioMedia
{
public void Play()
{
MessageBox.Show("Play the AudioMedia file.");
}
}
現在,我們引入了繼承的思想。
我們播放的只會是某種具體類型的音頻文件,因此,這個AudioMedia類并沒有實際使用的情況。對應在設計中,就是:這個類永遠不會被實例化。所以,還得動一下手術,將其改為抽象類。
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();
}
}
看看現在的設計,既滿足了類之間的層次關系,同時又保證了類的最小化原則,更利于擴展(到這里,你會發現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.");
}
}
雖然視頻和音頻格式不同,別忘了,他們都是媒體中的一種,很多時候,他們有許多相似的功能,比如播放。根據接口的定義,完全可以將相同功能的一系列對象實現同一個接口:
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類的演變,我們可以得出這樣一個結論:在調用類對象的屬性和方法時,盡量避免將具體類對象作為傳遞參數,而應傳遞其抽象對象,更好地是傳遞接口,將實際的調用和具體對象完全剝離開,這樣可以提高代碼的靈活性。