卓's profile成都,又是成都PhotosBlogListsMore Tools Help

Blog


    September 27

    Decorator

    场景:
        一个应用中,需要将一些文档进行打包传递,这些文档对象被定义为类A。
    出于安全的原因,对某些需要保密的文档需要加密,有一个新子类EncryptA来完成这个功能。
    由于某些文档很大,传递时为节省空间需要进行压缩,子类ZipA可以完成这个功能。
    某些文档在被阅读后,需要发送阅读回执给发送者,子类ReceiptA可以完成。
     
    现在有一些新的需求,我需要一个既可以加密,又可以压缩的对象,怎么实现?
    多重继承?这不是一个好办法,在这种情况下,继承关系之间形成了一个菱形关系。
    重新写一个新类ZipEncryptA?这是一个办法,可以解决这个问题。但是问题在于已经实现过的功能Encrypt和Zip可能需要在这个新的子类中再写一次;另外,一旦我还需要一个ZipEncryptReceipt功能的文档,上述的工作需要再实现一次。而且一旦这种可以组合的功能比较多,则子类的个数将会呈爆炸性的增长。
     
    这种情况下,考虑使用Decorator是一个不错的处理办法。
     
    结构:
        以前面所讲到的文档为例,有一个Document类作为我们需要操作的文档的基类。一个ConcreteDoc,这个类继承自Document,作为一种具体的文档,例如PdfDocument。我们还需要一个DecoratorDoc,这个类同样继承自Document,它是所有的Decorator的基类。DecoratorDoc有一个成员mDoc,这个成员指向一个Document对象。好了,在不使用Decorator模式的情况下,我们通常会这样使用:
        Document *pDoc = new PdfDocument();
        trans->transfer(pDoc);
    在Decorator模式下,如果需要对这个doc实现压缩功能,则实现一个ZipDecoratorDoc,它继承自DecoratorDoc,所有对Document的操作都将转发给mDoc来实现,并且其构造函数的参数中必须含有一个指向Document对象的指针。如果需要一些特别的操作,则可以进行附加。例如,对于getContent之类的操作,可以附加解压缩行为。setContent操作上可以附加压缩行为。那么,这种模式下上述的代码则变为:
        pDoc = new PdfDocument();
        trans->transfer(new ZipDecoratorDoc( pDoc ) );
    对于Document的使用者来说,它只知道操作的是一个Document。
    对于这种情况来说,还没有体现出Decorator的好处。因为我们可以通过继承的方式完成同样的功能。如果我们还需要同时对它进行加密,则我们依然从DecoratorDoc中派生出一个新类EncryptDecoratorDoc实现加密功能。则上述代码演化为:
        pDoc = new PdfDocument();
        trans->transfer(new EncryptDecoratorDoc( new ZipDecoratorDoc( pDoc ) ) );
    通过级联的方式,同时实现了加密和压缩的功能。
    这样,我们可以通过类的组合来实现功能的组合。
     
    这种方式比单纯的静态继承更灵活,添加实现某一功能的类要比添加多种功能的类更容易实现。通过类的组合的使用的方式,也减少了子类的个数。
    September 21

    BRIDGE桥接

    什么时候使用桥接?

    1.   当不希望一个抽象和它的接口之间存在一个固定的绑定关系时。例如,程序运行时刻希望实现可以被替换,比如,你需要一个collection,当数据不需要频繁增删时,用数组最方便,当数据频繁添加删除时,则链表比较合适,可以通过运行时刻的情况来决定使用哪种implementor。

    2.   希望类的抽象和其实现部分都可以有自己的继承层次。典型情况是,你正在为某一个标准编写一个实现(例如,为DOM标准编写实现),这个标准有自己的类层次,而你为其编写的实现类可能需要一个不完全相同的层次。在JAVA中由于有interface的存在这个问题很容易解决,对于C++来说则比较困难,多重继承可以解决部分问题,但是会带来一些麻烦。这时使用桥接模式将是一个好的解决办法。

    3.   对客户隐藏实现(C++),因为Implementor不需要提供头文件

    4.   对抽象进行修改时,实现部分代码不需要重新编译。(这算是一个附带的好处,对于代码特别庞大的工程,桥接模式将大量节省编译时间)

    5.   什么时候该使用桥接?对于C++代码,简单来说可以这样判断:如果你有一个抽象类,这个抽象类存在一个或多个派生类,并且这些派生类也是抽象类时,你需要考虑是不是用桥接模式来实现。

    September 20

    原型(PROTOTYPE)的应用范围?

    原型的应用场景:
    1。Client使用一个(或多个)Product
    2。Client作为框架,是通用的。Product作为被Client使用的产品,是一个抽象的类(接口)。在使用这个框架时,可能会新增加一些concrete product。
    3。Client在设计时,作为框架,它是不可能知道concrete Product是什么product,所以也无法去实例化一个具体的product。
    4。然而在应用中,某些时候Client也确实需要创建一个新的concrete。
    矛盾在于:
    Client需要创建新的对象(concrete product),但是又不知道新的concrete product如何创建,因为concrete product是框架(Client)完成之后新写的,Client无法调用其构造函数。
     
    解决办法:
    为product类声明一个clone方法,这个方法可以复制一个和当前对象一样的新对象,来达到创建新对象的目的。
    运行时刻,将新增加的类创建一个原型对象,并将这个对象交给client,这样,client在不知道product的类型的情况下却可以使用原型来创建新对象,达到动态加载的目的。
     
    实现原型模式的一些关键点:
    1。克隆操作:考虑深浅拷贝、循环引用等问题
    2。初始化:如何合理、方便的初始化新对象
    3。原型管理:有多个原型时,如何对其管理(可使用注册、关联等办法)
    February 09

    Abstract factory

    1. 目的:
      提供(生成)一系列相关或者相互依赖的对象的接口,而无需指定具体的类。
    2. 适用:
      • 一个系统要独立于产品的创建、组合和表示时。(也就是说:系统只关心使用某类产品,而不关心具体使用了哪个具体类。例如:一个图形应用需要创建一个窗口,则使用createWindow这个操作,而不关心createWindow是使用的MFC还是WIN32 SDK。这种情形在系统需要在不同平台间移植时尤为常见。)
      • 一个系统要用多个产品系列的其中一个来配置。(比如一个播放器可能有不同的外观,如果使用抽象工厂来创建外观,则皮肤界面技术非常容易实现)
      • 当强调一系列产品互相间的配合或联合使用时。
      • 当你只想提供一个产品的类库(接口),而不想显示他们的实现时。
    3. 实现:
      • 定义一个抽象工厂类 - AbstractFactory
      • 在这个类中,为每一个(抽象的)产品定义一个Create接口 - AbstractProduct
      • 以AbstractFactory为父类,将每一套可能的产品系列定义为具体类  - ConcreteFactory
      • 并真正的实现创建产品的代码(实现每一个Create接口) - ConcreteProduct。
      • 客户使用时,只使用AbstractFactory及AbstractProduct,而不接触具体类。
    4. 代码示例:
      class AbstractFactory
      {
      public:
          AbstractFactory();
          virtual void CreateProductA();
          virtual void CreateProductB();
      };
      
      class ConcreteFactory : public AbstractFactory
      {
      public:
          ConcreteFactory();
          virtual void CreateProductA();      //Do real creation work
          virtual void CreateProductB();
      };
      
      
    5. 优点:
      • 分离了具体类,使得客户没有直接接触到真正做工作的类。
      • 易于交互产品系列(其实也是便于移植)
      • 有利于产品的一致性
    6. 缺点:
      • 难以支持新种类的产品。需要使用新的产品时,往往需要改动所有具体类的接口。这个问题可以通过工厂方法得到一定的缓解。
    7. 讨论:
    8. 疑惑:
    February 06

    VISITOR - 访问者

    有些细节还在领悟中,To be continued。
    附加一个说明,有疑问之处我将用红色标注,我自己附加的理解使用下划线标注。
    1. 目的:
      在一个复杂的对象结构中,在不增加(修改)该对象各元素的类的情况下,增加、修改访问这些元素的新操作。
    2. 适用:
      • 一个对象结构由很多不同的元素构成,而我们需要针对不同的元素,实施不同的访问动作。
      • 需要对一个对象结构中的对象做很多不同、并且不相关的操作,这时你可能需要避免这些操作的代码“污染”这些对象的类。也就是说,可以令对象的类设计集中在对象本身的结构和功能之上,而一些其他操作,交给visitor来做,这样既可以将操作独立于这些对象之外,又可以将同类操作的代码集中放置,避免了将操作嵌入于对象的类之中。
      • 定义对象结构的类很少改变,而针对这些对象的操作需要经常变化。否则,你就需要不停的修改VISITOR的实现,以增加对新的对象的支持,其结果是可能反而令维护代码的代价变大。
    3. 实现:
      • 对象结构的所有元素有一个共同的父类Element,其中将定义一个Accept操作,以一个访问者为参数。
      • 定义一个Vistor类作为所有具体访问者的父类,在该类中,将为每一个具体的元素(Element)定义一个访问操作。该访问操作将以该元素自身作为参数。
      • 实现时,通过遍历对象结构中所有的元素,来完成访问。针对每一元素实例调用Accept操作,接受访问者,Accept操作中完成visitor对元素本身的访问。
      • 遍历可以由对象结构、访问者或者一个独立的迭代器来完成。
    4. 代码示例:
      class Visitor
      {
      public:
          virtual void VisitElementA(ElementA *);
          virtual void VisitElementB(ElementB *);
      protected:
          Visitor();
      };
      
      class Element
      {
      public:
          virtual ~Element();
          virtual void Accept(Visitor&) = 0;
      protected:
          Element();
      };
      
      
    5. 优点:
      • 便于增加新的操作,只需要定义新的visitor子类即可。
      • 同性质的访问操作集中在一个访问者子类中,避免了这些操作污染“元素”的代码。
      •  相对于迭代器,访问者访问的对象没有任何限制,可以不拥有共同的父类
      • 访问者在访问过程中可以累积状态。
    6. 缺点:
      • 增加新的元素比较麻烦,需要为每一个已经存在的visitor子类增加访问该类元素的操作。
      • 破坏了封装性。通常在访问过程中,访问者需要知道很多“元素”内部的细节。
    7. 讨论:
      单分派技术:通过请求和接收者的类型来确定真正被执行的代码。例如,一个操作
      object->VisitElementA(elementA);
      所执行的真正操作还需要由object的类型来确定。C++语言直接支持单分派。

      双分派技术(double-dispatch):需要通过请求和两个接受者的类型来确定真正被执行的代码。例如Accept就是一个双分派操作,它执行代码需要 由Visitor和Element的类型来确定。这样访问者就可以对每一个元素执行不同的操作。某些语言可以直接支持这一技术。
    8. 疑惑:
    January 27

    SINGLETON - 单件

    SINGLETON应该是设计模式中最简单易懂的一种,也是我在第一遍阅读中唯一记住的一种模式。
    1. 目的:
        SINGLETON的目的是确保一个类最多只存在一个实例。例如,一个系统中只能有一个键盘,也只能有一个鼠标等等。
    2. 适用:
      适用于一个类只能有一个实例的情况
    3. 实现:
      通常作法是将一个类的创建操作隐藏在类的一个公开操作中,并且让这个创建操作无法被外界访问到。这样,类的使用者只能通过公开的操作来访问类,而在这个操作内,我们就可以通过一个静态变量来保存这个类的唯一实例,并将这个唯一的实例提供给用户访问。
    4. 代码示例:
      class Singleton{
      public:
          static Singleton* Instance();
      protected:
          Singleton();
      private:
          static Singleton* _instance;
      };
      Singleton * Singleton::_instance = NULL;
      
      Singleton * Singleton::Instance()
      {
          if(_instance == 0)
          {
             _instance = new Singleton;
          }
          return _instance;
      }
      
    5. 优点:
      对这个唯一的实例的访问是受控的;其本质是对全局变量的一种改进;单件模式可以有子类,这样就允许扩展类的实例来细化具体的应用;单件模式很容易根据需要,改进为双件、多件模式(数目可控)。
    6. 缺点:
      暂时还没有想到
    7. 讨论:
      • 如何在过程语言中实现单件模式?
        我认为在实现的思想上和面向对象语言是一致的,在具体实现上,首先要将类的细节(结构)对用户隐藏;其次需要有类似于构造函数功能的创建函数来完成必须的创建工作,并将之声明为局部函数(static)。再次,通过一个全局函数来提供访问,使其成为访问的唯一途径。
      • 是否可以重载new操作符来实现SIGLETON?
        我觉得是可以的,但是具体实现细节待补充。
      • 多进程环境下的SINGLETON
        由于进程拥有独立的地址空间,那么单进程环境下SINGLETON的实现方式在多进程环境下必然存在问题。我想到的解决办法有:
        • 使用注册表
        • 建立服务器,采用C/S方式提供服务
        • More?
    8. 疑惑:
      关于效果一节中,书中谈到单件模式的优点时说“另一种封装单件功能的方式时使用类操作”,对该句感到不解,不知道如何实现?
    January 26

    开天辟地第一篇 读书前的动员和思想工作

    《设计模式》这本书,估计是在02年的夏天买的,到今天算算也有四个年头了。其中,在科银18楼我的桌面上躺了一年,借给老钱看了差不多一年,然后又继续在科银16楼躺了一年多,到了今天,我终于准备开始看了。

        其实一直很清楚模式对于设计的重要性:前辈们无数实践的经验总是比我一个毛头小子的知识来得丰富些吧?不过清楚归清楚,行动却总是没和思想觉悟保持一致。一方面因为该书比较抽象,读起来晦涩不容易理解;另一方面,也是主要原因,是因为我的懒惰。所以,过去的四年中,尽管开了无数次头,读书的计划却始终没有完成。

        今天终于下定决心,趁春节放假空暇把此书读完。促使我下定决心的原因是最近的项目中,使用到的方案其实就是设计模式中一个很典型的模式,由于我的无知,始终不能在代码中完美的实现,而是不断的花费时间去修正代码,其结果反而让结构更乱了。

        嗯,动员工作完成,每理解透一个模式,再上来做思想汇报。