重温设计模式(六)—— 阶段总结一

 1. 写在前面的

在文章开始之前,先写一些废话,不知不觉把重温设计模式写完了五篇。

<1> 重温设计模式(一)——享元模式(Flyweight) 

<2> 重温设计模式(二)——桥接模式(Bridge)

<3> 重温设计模式(三)——职责链模式(chain of responsibility)

<4> 重温设计模式(四)——工厂模式

<5> 重温设计模式(五)——我所理解的"抽象工厂"

下面的文章,算是对以上五篇文章的一个阶段性总结和反省。

首先,我得特别感谢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,Implementor2 i2,Implementor3 i3)
    {
        this.i1 = i1;
        this.i2 = i2;
        this.i3 = i3;
    }
    public void Run1()
    {
        i1.Run();
    }
    public void Run2()
    {
        i2.Run();
    }
    public void Run3()
    {
        i3.Run();
    }
}

abstract class Implementor1
{
    public abstract void Run();
}
abstract class Implementor2
{
    public abstract void Run();
}
abstract class Implementor3
{
    public abstract void Run();
}

 

这本就合情合理,每个人家门前可以根据自己的需求去建桥,又何必去规定每家门前只有一座桥呢?

另外,大家可以把重温设计模式(二)——桥接模式(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#),我们可以如何去改进这个设计模式,我们在实际应用中,如何去用模式+模式去更好地利用这个模式。

最后,说个事:

 

TerryLee的排名超过的园长dudu……………………

 

 

posted @ 2009-04-15 04:20  飞林沙  阅读(3342)  评论(7编辑  收藏  举报