Calvin's Tech Space

成于坚忍,毁于浮躁

   :: 首页 :: 联系 :: 聚合  :: 管理
 

有了翅膀才能,欠缺灵活的代就象坏了翅膀的儿。不能翔,就少了几的气韵。我需要码带去温暖的阳光,僵冷的翅膀重新起来。例,通过应OOP设计模式和重构,你会看到代是怎一步一步复活的。

了更好的理解设计思想,例尽可能简单化。但随着需求的增加,程序将越来越复。此就有修改设计的必要,重构和设计模式就可以派上用了。最后当设计渐趋完美后,你会发现,即使需求不断增加,你也可以神清气,不用码设计烦恼了。

假定我设计一个媒体播放器。媒体播放器目前只支持音文件mp3wav。如果不谈设计设计出来的播放器可能很简单

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;

}

}

}

自然,你会发现这设计非常的糟糕。因它根本没有未来的需求更提供最起展。如果你的设计结果是这样,那么当你为应接不暇的需求更而焦头烂额候,你可能更希望让这设计到它应该去的地方,就是桌面的回收站。仔分析段代,它其是一种最古老的面向设计。如果你要播放的不仅仅mp3wav,你会不断地增加相地播放方法,然后switch子句越来越,直至达到你视线看不到的地步。

好吧,我先来体验对象的精神。根据OOP的思想,我们应该mp3wav看作是一个独立的象。那么是这样吗

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的代实质并没有多大的化。

既然mp3wav都属于音文件,他都具有音文件的共性,什么不建立一个共同的呢?

public class AudioMedia {

public void Play() {

MessageBox.Show("Play the AudioMedia file.");

}

}

在我引入了承的思想,OOP也算是象模象了。得意之余,真分析现实世界吧。现实生活中,我播放的只会是某种具体型的音文件,因此AudioMedia并没有实际使用的情况。对应设计中,就是:不会被例化。所以,一下手,将其改抽象。好了,在的代有点OOP的感了:

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方法根本不用改

是不是到此就画上圆满的句号呢?然后刁的客是永不会足的,他在抱怨个媒体播放器了。因不想在看足球比候,只听到主持人的解,他更渴望看到足球明星在球奔跑的英姿。也就是,他希望你的媒体播放器能支持视频文件。你又痛苦了,因在更改硬件设计的同,原来的设计结构似乎出了问题。因为视频文件和音文件有很多不同的地方,你可不能偷懒让视频文件文件作父啊。你需要为视频文件设计另外的类对象了,假支持RMMPEG格式的视频

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钮执行。

该设计模式粉墨登了,种根据不同情况建不同型的方式,工厂模式是最拿手的。先看看我的工厂需要生哪些品呢?里有两种不同型的媒体AudioMediaVideoMedia(以后可能更多),但它又都实现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();

}

}

//其它工厂略;

写到里,也有人会什么不直接AudioMediaVideoMedia搭建工厂呢?很简单,因AudioMediaVideoMedia中,分别还有不同的型派生,如果搭建工厂,CreateMedia()方法中,仍然要使用Switch句。而且既然两个实现IMedia接口,可以认为是一种型,什么要那么麻请动抽象工厂模式,来生成两类产品呢?

可能会有人,即使你使用种方式,那么在判断具体建哪个工厂的候,不是也要用到switch?我承认这种看法是的。使用工厂模式,其直接好并非是要解决switch句的难题,而是要延迟对象的生成,以保的代的灵活性。当然,我有最后一招没有使出来,到后面你会发现switch句其会完全消失。

有一个问题,就是真的有必要实现AudioMediaVideoMedia两个抽象类吗其子直接实现接口不更简单于本文提到的需求,我想你是的,但不排除AudioMediaVideoMedia们还会存在区。例如音文件只需要提供声卡的接口,而视频文件需要提供给显卡的接口。如果MP3WAVRMMPEG直接实现IMedia接口,而不通AudioMediaVideoMedia,在足其它需求的设计上也是不合理的。当然不包括在本文的范畴了。

在主程序界面生了稍的改

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

IMediaFactory factory = (IMediaFactory)Activator.CreateInstance("MediaLibrary",factoryDllName).Unwrap();//MediaLibray引用的媒体文件及工厂的程序集;

IMedia media = factory.CreateMedia();

media.Play();

}

儿的翅膀不仅仅复活,有了可以的能力;同们还赋双翅膀更的功能,它可以得更高,得更

享受自由翔的意吧。想一下,如果我要增加某种媒体文件的播放功能,如AVI文件。那么,我只需要在原来的业务程序集中AVI,并实现IMedia接口,同时继VideoMedia。另外在工厂业务AVIMediaFactory,并实现IMediaFactory接口。假设这个新的工厂WingProject.AVIFactory在配置文件中添加如下一行:

<add key="AVI" value="WingProject.AVIFactory" />

而主程序呢?根本不需要做任何改,甚至不用重新编译双翅膀照可以自如地行!

posted on 2009-08-12 16:40 calvin 阅读(194) 评论(0)  编辑  收藏 所属分类: Design Patterns

只有注册用户登录后才能发表评论。


网站导航: