论软件实现之面向对象方法学

论软件实现之面向对象方法学配图
作为一名Android工程师,最熟悉的语言莫过于Java语言,而Java语言的核心思想就是面向对象,那么究竟何为面向对象,面向对象的开发过程是怎样的呢?下面就个人积累的开发经验做简单分享。

在《面向对象技术语典》中将“面向对象”定义为:一种使用对象、消息传递、类、继承、多态和动态绑定来开发问题域模型之解的范型。那么由此可见面向对象有几个重要的基本特征,即抽象性、分装性、继承性、多态性,这些特性贯穿着面向对象编程的始末。而面向对象方法与传统的软件工程方法相比,有很多优点,比如,面向对象与人们习惯的思维方式一致,这种对现实的抽象,再具体去描述现实的思想和在开发过程中解决复杂问题时逐步深化的渐进过程是一致的,面向对象也是降低系统复杂度的好方法,最重要的是,面向对象可以提高软件系统的可重用性,这样的复用对软件形成的效率提升和应用系统的维护以及扩展有着很大的好处,也可以说是程序开发者的一大福利。在Java语言形成的软件运行过程中,一切皆为对象,对象的产生必然依赖于相应类的实例的构造,从宏观的角度看,这种抽象和具体的对立关系也是客观世界的对立和统一的描述。

  • 方法

要想学会面向对象的分析思想,就必须能够对客观世界的系统进行建模,这样才能更好的明确问题需求,设计系统的实现框架。开发人员应该熟知的三种分析模型(对象模型、动态模型、功能模型)是面向对象设计的基础,在面向对象设计中,系统设计是对软件问题的解法的最高层策略,而对象设计则作为详细的主体部分,负责所有属性的可见性被定义。其实说白了,面向对象编程就是以类和对象为中心,把客观实体的功能以及其使用的数据封装起来然后逐渐搭建系统,使软件重用在面向对象开发中成为一种很自然很常见的开发模式。在系统实现的后期,面向对象的测试和维护是不可或缺的,面向对象的测试从单元测试、集成测试、系统测试到回归测试都是以面向对象为核心思想的,理解这些技术,也能很好地帮我们理解和把握面向对象思想的关键,利于我们的程序设计与开发。同样,理解是维护的基础,而重构“源设计图”则是理解的基础,因为对象的多态性、继承等特性会给理解原设计增加困难,为了日后维护的方便,应该在开发时就使软件的设计思想易于理解。

参考资料:殷锋《软件工程》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
有了翅膀才能飞,欠缺灵活的代码就象冻坏了翅膀的鸟儿。不能飞翔,就少了几许灵动的气韵。我们需要 给代码带去温暖的阳光,让僵冷的翅膀重新飞起来。结合实例,通过应用 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."); } } 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."); } } 现在我们引入了继承的思想,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 方法根本不用改变。 是不是到此就该画上圆满的句号呢?然后刁钻的客户是永远不会满足的,他们在抱怨这个媒体播放器了。 因为他们不想在看足球比赛的时候,只听到主持人的解说,他们更渴望看到足球明星在球场奔跑的英姿。 也就是说,他们希望你的媒体播放器能够支持视频文件。你又该痛苦了,因为在更改硬件设计的同时,原 来的软件设计结构似乎出了问题。因为视频文件和音频文件有很多不同的地方,你可不能偷懒,让视频文 件对象认音频文件作父亲啊。你需要为视频文件设计另外的类对象了,假设我们支持 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(); 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" />。 而主程序呢?根本不需要做任何改变,甚至不用重新编译,这双翅膀照样可以自如地飞行! 有了翅膀才能飞,欠缺灵活的代码就象冻坏了翅膀的鸟儿。不能飞翔,就少了几许灵动的气韵。我们需要 给代码带去温暖的阳光,让僵冷的翅膀重新飞起来。结合实例,通过应用 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."); } } 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."); } } 现在我们引入了继承的思想,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 方法根本不用改变。 是不是到此就该画上圆满的句号呢?然后刁钻的客户是永远不会满足的,他们在抱怨这个媒体播放器了。 因为他们不想在看足球比赛的时候,只听到主持人的解说,他们更渴望看到足球明星在球场奔跑的英姿。 也就是说,他们希望你的媒体播放器能够支持视频文件。你又该痛苦了,因为在更改硬件设计的同时,原 来的软件设计结构似乎出了问题。因为视频文件和音频文件有很多不同的地方,你可不能偷懒,让视频文 件对象认音频文件作父亲啊。你需要为视频文件设计另外的类对象了,假设我们支持 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(); 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" />。 而主程序呢?根本不需要做任何改变,甚至不用重新编译,这双翅膀照样可以自如地飞行!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值