假定我们要设计一个媒体播放器。该媒体播放器目前只支持音频文件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都属于音频文件,他们都具有音频文件的共性,为什么不为它们建立一个共同的父类呢?
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();
}
}
是不是到此就该画上圆满的句号呢?然后刁钻的客户是永远不会满足的,他们在抱怨这个媒体播放器了。因为他们不想在看足球比赛的时候,只听到主持人的解说,他们更渴望看到足球明星在球场奔跑的英姿。也就是说,他们希望你的媒体播放器能够支持视频文件。你又该痛苦了,因为在更改硬件设计的同时,原来的软件设计结构似乎出了问题。因为视频文件和音频文件有很多不同的地方,你可不能偷懒,让视频文件对象认音频文件作父亲啊。你需要为视频文件设计另外的类对象了,假设我们支持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的调用者。还记得文章最开始的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();
}
}
//其它工厂略;