理解面向对象

面向对象设计思想(C#)

有了翅膀才能飞,欠缺灵活的代码就象冻坏了翅膀的鸟儿。不能飞翔,就少了几许灵动的气韵。我们需要给代码带去温暖的阳光,

让僵冷的翅膀重新飞起来。结合实例,通过应用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语句仍然存在,

它好像是翅膀上滋生的毒瘤,提示我们,虽然翅膀已经从僵冷的世界里复活,但这双翅膀还是有病的,并不能正常地飞翔。

是使用配置文件的时候了。我们可以把每种媒体文件类类型的相应信息放在配置文件中,然后根据配置文件来选择创建具体的对象。

并且,这种创建对象的方法将使用反射来完成。首先,创建配置文件:

然后,在主程序界面的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();

//MediaLibray为引用的媒体文件及工厂的程序集;

IMediaFactory factory = (IMediaFactory)Activator.CreateInstance("MediaLibrary",factoryDllName).Unwrap();

IMedia media = factory.CreateMedia();

media.Play();

}

现在鸟儿的翅膀不仅仅复活,有了可以飞的能力;同时我们还赋予这双翅膀更强的功能,它可以飞得更高,飞得更远!

享受自由飞翔的惬意吧。设想一下,如果我们要增加某种媒体文件的播放功能,如AVI文件。

那么,我们只需要在原来的业务程序集中创建AVI类,并实现IMedia接口,同时继承VideoMedia类。

另外在工厂业务中创建AVIMediaFactory类,并实现IMediaFactory接口。假设这个新的工厂类型为WingProject.AVIFactory,

则在配置文件中添加如下一行:

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

本文示例源代码,请点击这里下载。

posted on 2005-01-25 12:36 battlerstar 阅读(1280) 评论(1)  编辑 收藏 收藏至365Key 所属分类: C#语言 、软件工程

评论

评论

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 16:10 Yuanlm

如何用上反射机制的话,连类工厂都不用的,只每个具体类上有个Attribute就行了。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 16:50 wayfarer

这其实是两种设计方法。因为工厂模式便于创建具体的产品,感觉更灵活些。主要原因还是我比较喜欢使用工厂模式吧。小新翻译过一篇文章《使用特性(attributes)和激活机制来实现工厂模式 》,使用了Attribute。链接地址是:

转自:http://battler.cnblogs.com/archive/2005/01/25/96954.aspx

# re: 面向对象设计思想(C#) 2005-01-26 08:27 battlerstar http://www2.cnblogs.com/wdxinren/archive/2004/11/25/68775.html

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 17:34 Paul

写的真好,谢谢。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 17:53 问问

我对模式运用的体会不深,问问:

工厂模式的一个典型就是访问不同的数据库,在配置文件中改一改就可以了,但前提是代码中已经实现了对不同数据库访问的操作。

而在此例子的意思是不会提前写好对不同媒体文件的操作代码,如果需要PLAY一个新类型还需要修改工厂和配置文件两处地方,如果我不用配置文件而是在主程序的switch处加代码的话,两者同样是修改两处地方,同样是要重新编译程序,在同样“麻烦”的情况下,我为什么非要用配置文件呢?

可能我问问题绕了个大圈,主要意思就是:在这个例子里,用配置文件的优势体现在哪里?在增加新的工厂类的情况下,用或不用配置文件改动量是一样的,为什么说修改主程序就成了这双翅膀还是有病的了呢?

菜鸟提问,还望大家海涵:)

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 18:36 wayfarer

呵呵:)这个问题很有意思,其实最初我也在想这个问题。那么怎么体会配置文件的妙处?对于本文的例子来说,应该是两层的分布。一个是表示层,一个是业务层。表示层就是我们的主程序,它提供了界面,供用户操作。而业务层则包括具体媒体文件的操作和工厂类。

如果是直接在主程序中直接创建具体类对象,并通过switch语句的话,那么在增加业务需求的时候,表示层和业务层都需要修改,并重新编译。而采用本文介绍的方法,你只需要改变业务层的类库文件即可,而表示层完全不用改变。这就体现了分层和松散耦合的精神。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 18:38 Yok

揪错字:视频是video不是vedio

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 19:32 wayfarer

呵呵,马上修改,谢谢Yok。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 20:55 小新0574

不错的文章,事实上我就在做一个类似的小软件学习,在类设计的时候遇到一些问题,你的文章很好的解决了我的一些疑惑,谢谢!

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 23:37 XiaoHui

现在一看到反射就想到了大家讲的性能的问题.

谈虎色变?呵呵.

这篇文章写得通俗易懂. 好文章. :)

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 23:44 冰汽水

讲的真好~循序渐进,谢谢

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-30 10:11 吕震宇

一看题目就很吸引人,只是今天没有时间读了,先收藏,改天再读。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-30 10:57 channelV

真的不错,上午读完了,对oop、工厂模式和重构有了进一步认识,谢谢楼主!

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-30 11:33 大漠孤烟

好文章,赞一下

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-30 16:15 QQ‘Richer

写得真的很棒,以前理解的比较模糊的概念这下清晰了很多。PFPF

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 15:43 dljsoft

不错不错!

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 15:51 冰汽水

wayfarer你好

回去把代码按照你的思路写了一遍,感觉很有意思,思路清晰了很多。

又照着结构画了个UML图,但是有一个问题,MP3MediaFactory类和IMedia接口之间de 关系应该算是什么?怎么在类图上说明呢?谢谢

是因为MP3继承了AudioMedia

public class MP3:AudioMedia{}

而AudioMedia又继承自IMedia接口

public abstract class AudioMedia:IMedia{}

所以,即使MP3MediaFactory类的CreateMedia方法定义的类型是IMedia,也仍然可以返回一个IMedia的子类——MP3类。对吗?

谢谢

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 16:26 wayfarer

@冰汽水:

这里,MP3类间接实现了IMedia接口。所以工厂在创建MP3类对象时,虽然返回的是IMedia,但根据多态的情况来判断,在运行时,它会将该IMedia对象认作是MP3类对象。

所以你在调用IMedia.Play()方法时,在运行时它会认为调用了MP3的Play方法。

你的UML图是正确的。当然,根据本文的需求,可以让MP3、RM等类直接实现IMedia接口,而无须加上AudioMedia和VideoMedia抽象类。之所以这样设计,也是从体系结构的角度来考虑的,感觉这样的层次关系更合理些。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 16:41 冰汽水

@wayfarer

明白了,谢谢。非常欣赏你这种写作风格。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 19:48 木头球

大侠果然厉害啊,精彩!

◎冰汽水:

1、问一句,你的UML图是用什么工具画的?是rose吗?我的rose怎么不能用这样的字体啊? 怎么设置啊?

2、对一个抽象类是继承还是实现啊?

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 20:06 wayfarer

看图的风格,应该是Rose。总之不会是Visio。

对类而言,都称为继承;对接口而言,都称为实现。

我代答了吧:)

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 21:28 冰汽水

@ 木头球:

我现在用的是Rational XDE for .net,因为设计的变化可以在代码中及时的反映出来,感觉比较容易上手。

@wayfarer:

我觉得这种设计思路中最好的就是,实现工厂接口后,可以返回一个已经实现了的业务接口,然后直接做业务操作即可。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-02 10:50 吕震宇

今天才读完,很好。To see a world in a grain of sand( 从一粒沙子看到一个世界)。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-06 13:10 wayfarer

后面读配置文件的代码稍有错误,已做了更改。并提供了源代码下载。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-11 01:56 小陆

采用工厂模式有什么用处不能看的太狭窄。也许从一个程序员的角度来看采用某种方式会提高他所面对的问题的复杂程度,造成他工作的效率反而下降了。效果要从整体考虑。

举个例子:客户给你一个要求,他说程序要在“IE5、IE6、Netscape7等浏览器”上运行。这个“等”字也许会给你的设计工作带来很多麻烦,进而成为需求上的一个风险。你怎么办?可以要求客户确定到底哪些浏览器,否则无法完成设计。也许到头来客户给你的答复仍然是不清楚的,这是很常见的情况。你也可以尽量使得设计更加的灵活,一面同客户确认,一面开始进行工作。

比如你设计了一个浏览器接口,所有的业务脚本都运行在这个接口之上。这个接口在IE或者Netscape上有不同的实现,接口的实例化由一个Factory负责。这样一来,你解除了业务代码和实际浏览器之间的耦合,一旦这个“等”字真成为一个麻烦,你不用查找修改所有的代码,而只要增加一个实现。

应用程序会伴适应着需求从简单走向复杂,好的设计可以为这个发展提供一个比较通畅的道路。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-22 13:41 信仰

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2005-01-05 20:30 冰汽水

如果我想让RM, MPGE类具有自己的一些特定属性的话怎么做呢?

factory.CreateMedia返回的是一个IMedia类型的对象,而IMedia只有Play方法,如果在IMedia中定义属性的话,那么RM, MPEG就必须都具备这些属性。

想让这些产品在实现相同的方法之外具备不同的属性,请指点一下。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2005-01-05 21:56 wayfarer

@冰汽水

你的这个问题非常好。如果有这样的情况,简单地修改配置文件就不行了。当然,本文的前提条件就是,这个接口应该作为公共的接口,客户端调用的是公共的行为。

我想了一下,也许采用Adapter模式,应该比较好。对于原有的设计修改较小。即为RM,Mpeg类具有的特定行为(你说的属性,不过我认为描述为行为更好)定义接口,如ISpecialMedia,然后用Adapter模式,让这两个类实现该接口。

当通过工厂创建出IMedia类时,通过判断其类对象是否为ISpecialMedia接口,如果是则显示转换为ISpecialMedia类型,调用其方法就OK了。

你提醒了我,等我试验之后,也许能写篇文章。

# re: 《让僵冷的翅膀飞起来》系列之一——从实例谈OOP、工厂模式和重构 2005-01-17 02:03 冰汽水

@wayfarer

前几天忙于工作,今晚一口气看完了系列二~五,感觉收获不小。但看到后面有点消化不良了,超出了我的理解范围。所以我还是回到这里继续我的简单问题。

其实我在上面提出的“子类的不同行为”这样的要求是打算用工厂模式封装一个DB操作类,原因一来是给自己一个实例学习各种模式,二来是希望能够简化一下.net的OleDB类。

说是简化,想来想去其实就是封装了一下DB的连接字符串。在实际开发过程中,我们经常要根据连接数据库的方式和数据库的类型来写连接字符串。比如:连一个库就要考虑使用OleDB(Jet 4.0)还是ODBC,然后再考虑目标数据库是Oracle还是SQL Server或者别的什么。

于是,我想写一个类,把以上的分类再封装一下,虽然这样作的意义并不一定很有效。

前些天写了一些代码,发现除了具备以下一些要素以外,还必须有一个特点,不知有没有适合的设计模式。

1.一个IDatabase接口(相当于IMedia了),定义了诸如Connection, Close, Begin这样的操作。这里我先简单的认为,宿主程序对各种需求的数据库的操作都是一样的。当然,还少不了一个DBFactory工厂类,实现CreateDatabase方法(类似IMediaFactory)

2.一个Database抽象类,实现IDatabase接口的各种方法(相当于VideoMedia)

3.与“播放器”例子不同的是,这个结构在Database基类的下面又多了OleDB和ODBC这样的子类,他们从Database基类继承,之所以这样是因为这两种连接方式中要定义不同或相同的属性,比如OleDB中定义了:DBPath, UserName, Password......;而ODBC中定义了:ODBCName, UserName, Password......。

4.在OleDB或ODBC下面再定义具体的Oracle, SQLServer, Access之类的子类,他们才真正具体的去实现Connection, Open, Close方法。(问题就出在这里了,后面再说)

这样,Client端根据不同的需求来实现:

DBFactory = new OracleFactory();

或者

DBFactory = new SQLServerFactory();

之后

再对DBFactory的ConnectionType属性赋值,然后通过CreateDatabse方法获得一个db实例,再对这个实例的不同属性赋值之后,使用Connection之类的方法获得DB的连接对象,再针对这个连接开始Begin...Close...

到今天整理了一下思路后回想,如果这种结构的话,就意味着我必须在以下一系列处理之后,让这个db实例能实时的具备不同的属性。

DBFactory = new OracleFactory(); //

DBFactory.ConnectionType = "OleDB" //

//然后得到一个DB实例

IDatabse db = DBFactory.CreateDatabase();

5.这时的db实例应该可以根据DBFactory.ConnectionType = "OleDB"还是DBFactory.ConnectionType = "ODBC"从而具备不同的属性(上面No.3提到DBPath, UserName, Password......或是ODBCName, UserName, Password)才好用。

问题就是出在步骤No.5上,什么样的模式才能让这些Oracle, SQLServer类动态的具备不同的属性呢?

(这些子类一定是和OleDB或ODBC的属性没有任何关系了,他们只是实现了所有DB都具备的一致性的操作而已)

说了一大堆,也不知道表达清楚没有。明天晚些时候再贴一些UML或代码给您,可能会更清楚的表达我的想法。

说真的,到今天再想想,这种封装实际意义不一定很大,仅做学习用。

谢谢!!

附:对您的《让僵冷的翅膀飞起来》系列由衷的敬佩,给我这种初学者提供了非常清晰的指导,希望有一天能赶上大家的步伐。

面向对象设计思想(C#)

有了翅膀才能飞,欠缺灵活的代码就象冻坏了翅膀的鸟儿。不能飞翔,就少了几许灵动的气韵。我们需要给代码带去温暖的阳光,

让僵冷的翅膀重新飞起来。结合实例,通过应用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语句仍然存在,

它好像是翅膀上滋生的毒瘤,提示我们,虽然翅膀已经从僵冷的世界里复活,但这双翅膀还是有病的,并不能正常地飞翔。

是使用配置文件的时候了。我们可以把每种媒体文件类类型的相应信息放在配置文件中,然后根据配置文件来选择创建具体的对象。

并且,这种创建对象的方法将使用反射来完成。首先,创建配置文件:

然后,在主程序界面的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();

//MediaLibray为引用的媒体文件及工厂的程序集;

IMediaFactory factory = (IMediaFactory)Activator.CreateInstance("MediaLibrary",factoryDllName).Unwrap();

IMedia media = factory.CreateMedia();

media.Play();

}

现在鸟儿的翅膀不仅仅复活,有了可以飞的能力;同时我们还赋予这双翅膀更强的功能,它可以飞得更高,飞得更远!

享受自由飞翔的惬意吧。设想一下,如果我们要增加某种媒体文件的播放功能,如AVI文件。

那么,我们只需要在原来的业务程序集中创建AVI类,并实现IMedia接口,同时继承VideoMedia类。

另外在工厂业务中创建AVIMediaFactory类,并实现IMediaFactory接口。假设这个新的工厂类型为WingProject.AVIFactory,

则在配置文件中添加如下一行:

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

本文示例源代码,请点击这里下载。

posted on 2005-01-25 12:36 battlerstar 阅读(1280) 评论(1)  编辑 收藏 收藏至365Key 所属分类: C#语言 、软件工程

评论

评论

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 16:10 Yuanlm

如何用上反射机制的话,连类工厂都不用的,只每个具体类上有个Attribute就行了。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 16:50 wayfarer

这其实是两种设计方法。因为工厂模式便于创建具体的产品,感觉更灵活些。主要原因还是我比较喜欢使用工厂模式吧。小新翻译过一篇文章《使用特性(attributes)和激活机制来实现工厂模式 》,使用了Attribute。链接地址是:

转自:http://battler.cnblogs.com/archive/2005/01/25/96954.aspx

# re: 面向对象设计思想(C#) 2005-01-26 08:27 battlerstar http://www2.cnblogs.com/wdxinren/archive/2004/11/25/68775.html

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 17:34 Paul

写的真好,谢谢。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 17:53 问问

我对模式运用的体会不深,问问:

工厂模式的一个典型就是访问不同的数据库,在配置文件中改一改就可以了,但前提是代码中已经实现了对不同数据库访问的操作。

而在此例子的意思是不会提前写好对不同媒体文件的操作代码,如果需要PLAY一个新类型还需要修改工厂和配置文件两处地方,如果我不用配置文件而是在主程序的switch处加代码的话,两者同样是修改两处地方,同样是要重新编译程序,在同样“麻烦”的情况下,我为什么非要用配置文件呢?

可能我问问题绕了个大圈,主要意思就是:在这个例子里,用配置文件的优势体现在哪里?在增加新的工厂类的情况下,用或不用配置文件改动量是一样的,为什么说修改主程序就成了这双翅膀还是有病的了呢?

菜鸟提问,还望大家海涵:)

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 18:36 wayfarer

呵呵:)这个问题很有意思,其实最初我也在想这个问题。那么怎么体会配置文件的妙处?对于本文的例子来说,应该是两层的分布。一个是表示层,一个是业务层。表示层就是我们的主程序,它提供了界面,供用户操作。而业务层则包括具体媒体文件的操作和工厂类。

如果是直接在主程序中直接创建具体类对象,并通过switch语句的话,那么在增加业务需求的时候,表示层和业务层都需要修改,并重新编译。而采用本文介绍的方法,你只需要改变业务层的类库文件即可,而表示层完全不用改变。这就体现了分层和松散耦合的精神。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 18:38 Yok

揪错字:视频是video不是vedio

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 19:32 wayfarer

呵呵,马上修改,谢谢Yok。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 20:55 小新0574

不错的文章,事实上我就在做一个类似的小软件学习,在类设计的时候遇到一些问题,你的文章很好的解决了我的一些疑惑,谢谢!

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 23:37 XiaoHui

现在一看到反射就想到了大家讲的性能的问题.

谈虎色变?呵呵.

这篇文章写得通俗易懂. 好文章. :)

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-29 23:44 冰汽水

讲的真好~循序渐进,谢谢

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-30 10:11 吕震宇

一看题目就很吸引人,只是今天没有时间读了,先收藏,改天再读。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-30 10:57 channelV

真的不错,上午读完了,对oop、工厂模式和重构有了进一步认识,谢谢楼主!

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-30 11:33 大漠孤烟

好文章,赞一下

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-11-30 16:15 QQ‘Richer

写得真的很棒,以前理解的比较模糊的概念这下清晰了很多。PFPF

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 15:43 dljsoft

不错不错!

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 15:51 冰汽水

wayfarer你好

回去把代码按照你的思路写了一遍,感觉很有意思,思路清晰了很多。

又照着结构画了个UML图,但是有一个问题,MP3MediaFactory类和IMedia接口之间de 关系应该算是什么?怎么在类图上说明呢?谢谢

是因为MP3继承了AudioMedia

public class MP3:AudioMedia{}

而AudioMedia又继承自IMedia接口

public abstract class AudioMedia:IMedia{}

所以,即使MP3MediaFactory类的CreateMedia方法定义的类型是IMedia,也仍然可以返回一个IMedia的子类——MP3类。对吗?

谢谢

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 16:26 wayfarer

@冰汽水:

这里,MP3类间接实现了IMedia接口。所以工厂在创建MP3类对象时,虽然返回的是IMedia,但根据多态的情况来判断,在运行时,它会将该IMedia对象认作是MP3类对象。

所以你在调用IMedia.Play()方法时,在运行时它会认为调用了MP3的Play方法。

你的UML图是正确的。当然,根据本文的需求,可以让MP3、RM等类直接实现IMedia接口,而无须加上AudioMedia和VideoMedia抽象类。之所以这样设计,也是从体系结构的角度来考虑的,感觉这样的层次关系更合理些。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 16:41 冰汽水

@wayfarer

明白了,谢谢。非常欣赏你这种写作风格。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 19:48 木头球

大侠果然厉害啊,精彩!

◎冰汽水:

1、问一句,你的UML图是用什么工具画的?是rose吗?我的rose怎么不能用这样的字体啊? 怎么设置啊?

2、对一个抽象类是继承还是实现啊?

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 20:06 wayfarer

看图的风格,应该是Rose。总之不会是Visio。

对类而言,都称为继承;对接口而言,都称为实现。

我代答了吧:)

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-01 21:28 冰汽水

@ 木头球:

我现在用的是Rational XDE for .net,因为设计的变化可以在代码中及时的反映出来,感觉比较容易上手。

@wayfarer:

我觉得这种设计思路中最好的就是,实现工厂接口后,可以返回一个已经实现了的业务接口,然后直接做业务操作即可。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-02 10:50 吕震宇

今天才读完,很好。To see a world in a grain of sand( 从一粒沙子看到一个世界)。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-06 13:10 wayfarer

后面读配置文件的代码稍有错误,已做了更改。并提供了源代码下载。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-11 01:56 小陆

采用工厂模式有什么用处不能看的太狭窄。也许从一个程序员的角度来看采用某种方式会提高他所面对的问题的复杂程度,造成他工作的效率反而下降了。效果要从整体考虑。

举个例子:客户给你一个要求,他说程序要在“IE5、IE6、Netscape7等浏览器”上运行。这个“等”字也许会给你的设计工作带来很多麻烦,进而成为需求上的一个风险。你怎么办?可以要求客户确定到底哪些浏览器,否则无法完成设计。也许到头来客户给你的答复仍然是不清楚的,这是很常见的情况。你也可以尽量使得设计更加的灵活,一面同客户确认,一面开始进行工作。

比如你设计了一个浏览器接口,所有的业务脚本都运行在这个接口之上。这个接口在IE或者Netscape上有不同的实现,接口的实例化由一个Factory负责。这样一来,你解除了业务代码和实际浏览器之间的耦合,一旦这个“等”字真成为一个麻烦,你不用查找修改所有的代码,而只要增加一个实现。

应用程序会伴适应着需求从简单走向复杂,好的设计可以为这个发展提供一个比较通畅的道路。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2004-12-22 13:41 信仰

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2005-01-05 20:30 冰汽水

如果我想让RM, MPGE类具有自己的一些特定属性的话怎么做呢?

factory.CreateMedia返回的是一个IMedia类型的对象,而IMedia只有Play方法,如果在IMedia中定义属性的话,那么RM, MPEG就必须都具备这些属性。

想让这些产品在实现相同的方法之外具备不同的属性,请指点一下。

# re: 让僵冷的翅膀飞起来——从实例谈OOP、工厂模式和重构 2005-01-05 21:56 wayfarer

@冰汽水

你的这个问题非常好。如果有这样的情况,简单地修改配置文件就不行了。当然,本文的前提条件就是,这个接口应该作为公共的接口,客户端调用的是公共的行为。

我想了一下,也许采用Adapter模式,应该比较好。对于原有的设计修改较小。即为RM,Mpeg类具有的特定行为(你说的属性,不过我认为描述为行为更好)定义接口,如ISpecialMedia,然后用Adapter模式,让这两个类实现该接口。

当通过工厂创建出IMedia类时,通过判断其类对象是否为ISpecialMedia接口,如果是则显示转换为ISpecialMedia类型,调用其方法就OK了。

你提醒了我,等我试验之后,也许能写篇文章。

# re: 《让僵冷的翅膀飞起来》系列之一——从实例谈OOP、工厂模式和重构 2005-01-17 02:03 冰汽水

@wayfarer

前几天忙于工作,今晚一口气看完了系列二~五,感觉收获不小。但看到后面有点消化不良了,超出了我的理解范围。所以我还是回到这里继续我的简单问题。

其实我在上面提出的“子类的不同行为”这样的要求是打算用工厂模式封装一个DB操作类,原因一来是给自己一个实例学习各种模式,二来是希望能够简化一下.net的OleDB类。

说是简化,想来想去其实就是封装了一下DB的连接字符串。在实际开发过程中,我们经常要根据连接数据库的方式和数据库的类型来写连接字符串。比如:连一个库就要考虑使用OleDB(Jet 4.0)还是ODBC,然后再考虑目标数据库是Oracle还是SQL Server或者别的什么。

于是,我想写一个类,把以上的分类再封装一下,虽然这样作的意义并不一定很有效。

前些天写了一些代码,发现除了具备以下一些要素以外,还必须有一个特点,不知有没有适合的设计模式。

1.一个IDatabase接口(相当于IMedia了),定义了诸如Connection, Close, Begin这样的操作。这里我先简单的认为,宿主程序对各种需求的数据库的操作都是一样的。当然,还少不了一个DBFactory工厂类,实现CreateDatabase方法(类似IMediaFactory)

2.一个Database抽象类,实现IDatabase接口的各种方法(相当于VideoMedia)

3.与“播放器”例子不同的是,这个结构在Database基类的下面又多了OleDB和ODBC这样的子类,他们从Database基类继承,之所以这样是因为这两种连接方式中要定义不同或相同的属性,比如OleDB中定义了:DBPath, UserName, Password......;而ODBC中定义了:ODBCName, UserName, Password......。

4.在OleDB或ODBC下面再定义具体的Oracle, SQLServer, Access之类的子类,他们才真正具体的去实现Connection, Open, Close方法。(问题就出在这里了,后面再说)

这样,Client端根据不同的需求来实现:

DBFactory = new OracleFactory();

或者

DBFactory = new SQLServerFactory();

之后

再对DBFactory的ConnectionType属性赋值,然后通过CreateDatabse方法获得一个db实例,再对这个实例的不同属性赋值之后,使用Connection之类的方法获得DB的连接对象,再针对这个连接开始Begin...Close...

到今天整理了一下思路后回想,如果这种结构的话,就意味着我必须在以下一系列处理之后,让这个db实例能实时的具备不同的属性。

DBFactory = new OracleFactory(); //

DBFactory.ConnectionType = "OleDB" //

//然后得到一个DB实例

IDatabse db = DBFactory.CreateDatabase();

5.这时的db实例应该可以根据DBFactory.ConnectionType = "OleDB"还是DBFactory.ConnectionType = "ODBC"从而具备不同的属性(上面No.3提到DBPath, UserName, Password......或是ODBCName, UserName, Password)才好用。

问题就是出在步骤No.5上,什么样的模式才能让这些Oracle, SQLServer类动态的具备不同的属性呢?

(这些子类一定是和OleDB或ODBC的属性没有任何关系了,他们只是实现了所有DB都具备的一致性的操作而已)

说了一大堆,也不知道表达清楚没有。明天晚些时候再贴一些UML或代码给您,可能会更清楚的表达我的想法。

说真的,到今天再想想,这种封装实际意义不一定很大,仅做学习用。

谢谢!!

附:对您的《让僵冷的翅膀飞起来》系列由衷的敬佩,给我这种初学者提供了非常清晰的指导,希望有一天能赶上大家的步伐。


相关文章

  • 面向对象方法与结构化方法的比较
  • 结构化方法和面向对象方法的比较 结构化方法是结构化分析,结构化设计和结构化编程的总称,是最早最传统的软件开发方法,也是迄今为止信息系统中应用最普遍.最成熟的一种,它引入了工程思想和结构化思想,使大型软件的开发和编程都得到了极大的改善. 面向 ...查看


  • 面向对象分类
  • 2010年 第4期 张春晓, 等:基于面向对象和影像认知的遥感影像分类方法---以都江堰向峨乡区域为例11 文章编号:049420911(2010) 0420011204中图分类号:P237 文献标识码:B 基于面向对象和影像认知的遥感影像 ...查看


  • 面向对象的软件开发过程
  • Science & Technology Vision 2 IT论坛 科技视界 2012年7月第19期 3 4 面向对象的软件开发过程 5 周媛媛 6 (大庆石油科技馆 黑龙江大庆163000) 7 [摘 要]本文介绍了面向对象技术 ...查看


  • 面向对象程序设计的教学改革
  • 面向对象技术现在已经成为计算机科学与技术中的一个重要领域,并渗透到计算机学科的很多方向.面向对象的思想方法属于计算机专业学生必须学习掌握的核心课程内容. 由于技术发展日新月异,计算机专业教学特别是教材编写已经相对滞后,面向对象程序设计课程亦 ...查看


  • 面向对象的分析过程
  • 面向对象的分析过程 摘要 分析了面向对象技术应用于仿真领域的种种优点,亦即我们选择采用面向对 象的方法进行改造的原因.一些面向对象方法存在的不足. 关键词:仿真,面向对象,岸边集装箱起重机,训练器 面向对象的开发方法是以对象作为最基本的元素 ...查看


  • 俄罗斯方块系统设计文档
  • 俄罗斯方块 系统设计说明书 版本号:V1.0 班 级: 项目组长: 小组成员: 任课教师: 职 称: 日 期: 更改控制页 目 录 第一章 前言 . ............................................. ...查看


  • 面向对象概念
  • 一.OOP(object oriented programming)面向对象编程 两个概念: 类:(抽象的):把所有对象,像的部分(数据和功能)抽出来进行描述. 类是对象的模版. 例如:人类(名字,年龄,吃,睡觉,学习),猫类,显示器(数据 ...查看


  • [软件工程导论]考试夹带
  • 1.软件危机的概念:软件危机是指在计算机软件的开发和维护过程中所遇到的一系 列严重的问题. 2.产生软件危机的原因:一方面与软件本身的特点有关,另一方面也和软件开发与 维护的方法不正确有关. 3.软件工程的定义:是指导计算机软件开发和维护的 ...查看


  • 软件开发案例分析课程
  • <软件开发案例分析>课程 教学大纲 大连职业技术学院 2005年 9月 一. 课程名称: <软件开发案例分析> 二. 学分: (4学分) 三. 适用专业: 计算机软件设计专业.计算机软件设计专业(日语) 四. 教学目 ...查看


  • 软件设计模式
  • 软件设计模式浅析 木子田心街 (河南 郑州) 摘 要:本文主要从设计模式的几个方面进行简单的概括和研究,从简单的工厂模式到Coordinator模式,以及对设计模式在未来的展望做出了进一步的分析和总结. 关键词:简单工厂模式:Coordin ...查看


热门内容