周玉鳳
[摘 要]:2005年初,筆者在北京參加了北大青鳥教師培訓(xùn)班,學(xué)習(xí)的內(nèi)容是C#。之后,經(jīng)過幾年的教學(xué)實(shí)踐,筆者對面向?qū)ο蟪绦蛟O(shè)計(jì)有了更為深廣的理解,本文筆者就談?wù)勛约旱恼J(rèn)識與體會,以與中職計(jì)算機(jī)專業(yè)學(xué)生朋友交流。
[關(guān)鍵詞]:中職生 面向?qū)ο蟪绦蛟O(shè)計(jì) C#
首先,筆者談幾點(diǎn)理解面向?qū)ο蟪绦蛟O(shè)計(jì)的要點(diǎn):
封裝性:數(shù)據(jù)和行為封裝在一個自定義的數(shù)據(jù)類型里面,其作用:(1)把松散的方法聚合到數(shù)據(jù)類型里面。(2)隱藏?cái)?shù)據(jù)類型內(nèi)部的數(shù)據(jù)定義。
繼承性:把多個數(shù)據(jù)類型按照一定的方法進(jìn)行抽象,把共有的屬性放的一個高層的數(shù)據(jù)類型里面,然后底層的多個類型可以共享這個高層類型的數(shù)據(jù)和方法。作用有兩個:(1)簡化數(shù)據(jù)類型的定義,減少重復(fù)定義。(2)使組織結(jié)構(gòu)清晰化,這和現(xiàn)實(shí)世界的分門別類具有異曲同工之妙。
多態(tài)性:一個方法,可以根據(jù)其操縱的具體類型的對象的不同(抽象類型相同),可以有不同的表現(xiàn)。其作用:(1)進(jìn)一步簡化方法的定義。(2)是多種不同的方法具有統(tǒng)一的接口,方便調(diào)用。
接下來,我們結(jié)合實(shí)例說說設(shè)計(jì)模式。
為了更好地理解設(shè)計(jì)思想,實(shí)例盡可能簡單化。但隨著需求的增加,程序?qū)⒃絹碓綇?fù)雜。此時(shí),就有修改設(shè)計(jì)的必要,重構(gòu)和設(shè)計(jì)模式就可以派上用場了。最后當(dāng)設(shè)計(jì)漸趨完美后,你會發(fā)現(xiàn),即使需求不斷增加,你也可以神清氣閑,不用為代碼設(shè)計(jì)而煩惱了。
假定我們要設(shè)計(jì)一個媒體播放器。該媒體播放器目前只支持音頻文件mp3和wav。如果不談設(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;
}
}
}
你會發(fā)現(xiàn),這個設(shè)計(jì)有很大的隱患,它根本沒有為未來的需求變更提供最起碼的擴(kuò)展。仔細(xì)分析這段代碼,它其實(shí)是一種最古老的面向結(jié)構(gòu)的設(shè)計(jì)。如果你要播放的不僅僅是mp3和wav,你會不斷地增加相應(yīng)地播放方法,然后讓switch子句越來越長,直至達(dá)到你視線看不到的地步。
我們來體驗(yàn)面向?qū)ο蟮乃枷搿0裮p3和wav看作是一個獨(dú)立的對象。
public class MP3
{
public void Play()
{
MessageBox.Show("Play the mp3 file.");
}
}
public class WAV
{
public void Play()
{
MessageBox.Show("Play the wav file.");
}
}
統(tǒng)一的Play()方法。在后面的設(shè)計(jì)中,會發(fā)現(xiàn)這樣改名是很關(guān)鍵的!
但以現(xiàn)在的方式去更改MediaPlayer的代碼,實(shí)質(zhì)并沒有多大的變化。
既然mp3和wav都屬于音頻文件,他們都具有音頻文件的共性,我們就應(yīng)該建立一個共同的父類。
public class AudioMedia
{
public void Play()
{
MessageBox.Show("Play the AudioMedia file.");
}
}
現(xiàn)在,我們引入了繼承的思想。
我們播放的只會是某種具體類型的音頻文件,因此,這個AudioMedia類并沒有實(shí)際使用的情況。對應(yīng)在設(shè)計(jì)中,就是:這個類永遠(yuǎn)不會被實(shí)例化。所以,還得動一下手術(shù),將其改為抽象類。
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();
}
}
看看現(xiàn)在的設(shè)計(jì),既滿足了類之間的層次關(guān)系,同時(shí)又保證了類的最小化原則,更利于擴(kuò)展(到這里,你會發(fā)現(xiàn)play方法名改得多有必要)。
即使你現(xiàn)在又增加了對WMA文件的播放,只需要設(shè)計(jì)WMA類,并繼承AudioMedia,重寫Play方法就可以了,MediaPlayer類對象的Play方法根本不用改變。
然而,如果要求設(shè)計(jì)的媒體播放器能夠支持視頻文件。怎么辦呢?
原來的軟件設(shè)計(jì)結(jié)構(gòu)似乎出了問題,視頻文件和音頻文件有很多不同的地方。解決起來也不難,讓視頻文件對象認(rèn)音頻文件作父親啊。需要為視頻文件設(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ù)接口的定義,完全可以將相同功能的一系列對象實(shí)現(xiàn)同一個接口:
public interface IMedia
{
void Play();
}
public abstract class AudioMedia:IMedia
{
public abstract void Play();
}
public abstract class VideoMedia:IMedia
{
public abstract void Play();
}
再更改一下MediaPlayer的設(shè)計(jì)就OK了:
public class MediaPlayer
{
public void Play(IMedia media)
{
media.Play();
}
}
總結(jié)一下,從MediaPlayer類的演變,我們可以得出這樣一個結(jié)論:在調(diào)用類對象的屬性和方法時(shí),盡量避免將具體類對象作為傳遞參數(shù),而應(yīng)傳遞其抽象對象,更好地是傳遞接口,將實(shí)際的調(diào)用和具體對象完全剝離開,這樣可以提高代碼的靈活性。