1. 写在前面的
下面的文章,算是对以上五篇文章的一个阶段性总结和反省。 首先,我得特别感谢winter-cn不厌其烦地指出我在设计模式应用中的一个又一个错误,如果没有他,很多东西我还是会继续地错下去。 另外,也感谢博客园众多园友对我的鼓励,让我有勇气再次写下去。 2. 步入正题1——享元模式 享元模式运用共享技术有效地支持了大量细粒度的对象,解决了大量对象造成的内存开销问题。 何时采用享元模式:
在这里我补充两个很重要的概念:内蕴状态和外蕴状态。内蕴状态是共享对象中被所有对象所共享的东西,而外蕴状态是由外部调用者传进去的东西。 我们这里就借助Word文档的例子来说明这两个概念,转了一圈,再次回到GOF: 在一篇文章中,一共只有26个字母,但是这篇文章却可能有几万个字。那么这个时候,我们就可以用享元模式来解决问题。 这个时候什么是内蕴状态?什么是外蕴状态? 内蕴状态被所有调用者所共享,也就是那26个字母。 而外蕴状态呢?就是每个字符所不可能不同的字体。看清楚,是可能不同。 如果写成示例代码: class Character { char c; Font f; private List<Font> fontList; public Font F { set { int i = 0; for (i = 0; i < fontList.Count; i++) { if (fontList[i].Equals(value)) { this.f = fontList[i]; break; } } fontList.Add(value); f = value; } } } class Font { string fontName; string color; int size; // and so on; }
3. 享元模式——原文反思 在我的原文重温设计模式(一)——享元模式(Flyweight) 中,我错在了哪? 回到我QQ聊天的那个例子: 在Chat对象中,我有三个字段,string boyName,string girlName,string content。 在文中,我将boyName+ _ +girlName作为了内蕴状态,而将content作为了外蕴状态。 这个从表面上来看没有错,但是实际错在哪里? 在享元模式的使用条件中有着这样一条:如果删除对象的外蕴状态,那么可以用相对较少的共享对象去取代很多组对象,此时可以考虑享元模式。 先结合GOF26个字母的那个例子来分析这句话:26个字母,也就是26组,每个字母可能有着不同的字体,于是也就是每组又有着若干个对象,如果我们把字体这个外蕴条件删除,那么这26组就可以用26个对象去表示。 好了,来看我的例子: boyName+_+girlName作为内蕴状态:这个我认为并无错误,虽然在GOF的模式例子中,26个组可以说是已经搞定好的,但是我在QQ聊天的这个例子中,动态地去添加组,我认为这点并无不可。 这个项目的问题在于外蕴状态,我们知道每一次和每一次的聊天内容,也就是content是不同的,我把这个content去作为外蕴状态,本身就没有共享,重利用的意义。 继续用上面那个组的概念去说话。假设现在有100对男女在聊天,那么根据boyName+_+girlName,可以把所有对象分成100组,由于这每组对象的content是不同的,也就是每次回发给服务器时,这个content都无法被重利用,所以说100组对象,共有100个对象,而这个100个对象,把外蕴状态剔除掉,还是100个对象。 因此说,这个例子是不适合享元模式的。 4. 享元模式——垂死挣扎(享元模式究竟享什么?) 此处声明,此处为垂死挣扎区,所以这里只是个人意见,纯属扯淡,请初学者选择性相信。理解有误,希望各位指教。 究竟什么是享元模式,享元模式无非就是通过共享技术去支持大量大粒度对象,使存储开销减小。 还是考虑原文中String的应用,这是个标准的享元模式,我想这点无人质疑。那么我们看String共享的是什么?String对象,内蕴状态是这个字符串的值,那么外蕴状态呢?其实就是他所指向的地址。 我们先用享元模式去实现String。 class String1 { string content; string address; private List<string> stringList; public string Address { set { int i = 0; for (i = 0; i < stringList.Count; i++) { if (stringList[i].Equals(value)) { this.address = stringList[i]; break; } } stringList.Add(value); address = value; } } }
的确,每一个content都对应了一个address,而且共享了address。但我们想一下,若干个相同的content对应的是不是同一个address呢?也就是说,当content固定下来,他的address也就随之固定了下来。 与其这样,那我们还不如这样去写:Dictionary<string,string> dic=new Dictionary<string,string>();dic.Add(content,address)。 这又何乐而不为呢? 所以说,我认为,享元模式本身是一个宏观概念。享的什么不重要,总之,目的只有一个,节省内存。至此而已! 5. 步入正题2——桥接模式 桥接模式, GOF这样去解释他:将抽象部分和实现部分分离,使他们都可以独立地变化。 我个人认为这个是组合优于继承设计原则的一个很棒的体现。 6. 桥接模式 —— 原文反思 我的例子是不是桥接模式?很明确的,不是! 这里就要提到一个概念的解释。GOF说,讲抽象和实现部分相分离。这里的实现是什么意思? 这个实现并非是我们平时说的“实现”某一接口的实现。而是可以这样解释: 对于同样一个Work()方法来说,对于程序员来说,Work是编程,对于工人来说,是操作机器,对于教师来说,是教书。 代码如下: interface IWork { void Work(); } class Programer : IWork { #region IWork 成员 public void Work() { Console.WriteLine("写代码"); } #endregion } class Teacher : IWork { #region IWork 成员 public void Work() { Console.WriteLine("教书"); } #endregion } 然后将这个具体的实现注入到我们的应用类中,如在上文中的注入方式极为SetWork(IWork work){}; 那么我原文中,Photoshop的例子错在哪里? 我错在了对实现的错误理解上,因此我的例子只是一个单纯的组合优于继承。 也就是说,桥接模式只是组合优于继承的一个应用,但是并非等于组合优于继承。桥接模式本身的目的解决的是由于实现而带来的类爆炸问题。 7. 桥接模式——绝地反击winter-cn(桥接模式只有一个桥么?) 再次声明,此处为绝地反击区,所以这里只是个人意见,纯属扯淡,请初学者选择性相信。理解有误,希望各位指教。 |
这样可否呢? 举个例子:我们要做Windows和Linux,Windows和Linux下的QQ游戏实现是不同的,Windows和Linux下的记事本实现是不同的,Windows和Linux下的*****实现是不同的,那么他们就应该都分别抽出来形成一个桥。 然后我们原本的代码是: class Program { private Implementor i; public void SetImplementor(Implementor i) { this.i = i; } public void Run() { i.Run(); } } abstract class Implementor { public abstract void Run(); }
但是我们在此处就如此实现: class Program { private Implementor1 i1; private Implementor2 i2; private Implementor3 i3; public void SetImplementor(Implementor1 i1,
这本就合情合理,每个人家门前可以根据自己的需求去建桥,又何必去规定每家门前只有一座桥呢? 另外,大家可以把重温设计模式(二)——桥接模式(Bridge)当作重温设计模式(二)——组合优于继承去理解,也会是一篇好文哦! 8. 补充说明——职责链模式 关于职责连模式,在这里我只是想补充一点:关于职责链和链表的区别。 在文中,我提到了职责链和链表的区别,在这里加以补充。 最近我在写一个关于工作流的入门文章,我们知道,工作流分为顺序工作流和状态机工作流。工作流其实就是一种复杂模式下的,有成熟框架,有图形化表示的职责链。 那么当然,职责链按理来说也是应该支持状态机的。 我在这里引用一个我画的关于状态机工作流的图: |
也就是说,在状态机中,可以出现A.Next=b;b.Next=a;的情况,但是在链表中就绝对不会出现这种情况。 原因在于,一个职责链的每一个节点,可以引出多个Next指针,关于这点,大家可以参考我原文中的职责链的扩展——树状链结构。 9. 再说工厂 在这里,我只是想针对我的原文进行一个再说明。 首先是我在测试代码中,乃至全文都说的无模式。 那个无模式真的是无模式么?其实,我们更应该将他理解为一个伪工厂。工厂究竟是来做什么的? 这个从抽象谈起,我们在设计软件的时候,在设计的就是那么一个抽象。而这个工厂的Create,其实就是对创建对象的一个抽象化。 那么我所谓的无模式,其实也是将这个创建产品的过程给单独地提取抽象了出来。可以说是一个简化版的工厂,或者是简化版的简单工厂。 这个无模式有个什么样的局限性呢?当我们有一个产品,既用了ProductA,也用了ProductB,而且这种情况是非常常见的。 那么这个时候,我的简化版“伪”工厂就无法实现需求了。 总之,什么样的设计取决于什么样的变化! 10. 步入正题3——抽象工厂 抽象工厂解决的问题是一个产品族的问题。 什么是产品族,我之前的解释有些问题。产品族不是可以随意搭配的。抽象工厂仅提供有效的组合,而对于随意的组合就不是抽象工厂所能解决问题的范畴。 11. 抽象工厂——原文反思 在原文中,我举到了一个汽车的例子,那个例子就不是一个抽象工厂所能解决范围之内的例子。 回到我们之前的随意组合和有效组合的问题。 油漆的颜色,与发动机的引擎,再到轮子的型号,这三者之间是没有一个互相约束的联系的。也就是说,无论什么颜色的油漆,都可以配任意型号的引擎。也就是说如果有3种油漆,4种引擎,那么就有3*4=12种搭配,这个就叫做随意组合,是不可以用抽象工厂来解决的。 比如这样的例子就很合理了,操作系统和应用软件的关系,比如现在有两种系统,Windows,Linux,有三种应用软件:QQ,Office,***(比如这个是只能在Linux上运行的),那么他们之间就只有Windows+QQ,Linux+QQ,Windows+Office,Linux+****4种组合,这样我们就把他叫做有效组合,而非随意组合,这个才是抽象工厂解决的范畴。 12. 抽象工厂—— 垂死挣扎 排除我的原文中,CPU和显卡例子不合理外,各位可以参考我在其他例子及章节处,个人感觉观点不无道理。 13. 总结 很多问题,很多模式,也许我们现在并不常用,拿当前的C#,Java等语言来说,他们可以为我们做太多的事情。 例如说,迭代器模式,观察者模式,这些在C#中都被简化了许多许多,甚至已经快废弃掉了。 但是我们学习模式学习的是一种设计思路,一种思维,首先,我们可以抛去那些高级特性,只去单纯地想面向对象。 然后我们可以去想着,用当今的语言特性(如C#),我们可以如何去改进这个设计模式,我们在实际应用中,如何去用模式+模式去更好地利用这个模式。(本文由控件中国网转载) |