设计模式简要总结——行为型模式

来源:互联网 发布:软件运维工程师 知乎 编辑:程序博客网 时间:2024/05/22 12:16
1. chain of responsibility 职责链
  
   使用: · 有多个对象可以处理一个请求, 而具体由哪个对象处理请求需要在运行时刻自动确定。
          · 可以处理一个请求的对象集合应被动态指定
         
   原理: 在链上的每个对象都有一致的处理请求的接口, 和访问链上后继者的接口。
          从链上的第一个对象开始,要么亲自处理他,要么转发给链中的下一个候选者。
          提交请求的客户并不明确知道哪一个对象会处理他,提交请求的可以并不直接引用最终响应他的对象。
  
   实现:class HelpHandler ///////// 基类,维护链的父节点引用(successor)
         {
              HelpHandler(HelpHander * successor):_successor(successor){}
              virtual void HandleEvent(Event e)
              {
                  _successor->HandleEvent(e);
              }
         private:
             HelpHandler * _successor;
         };
         class Bottun: public HelpHandler
         {
             Bottun(HelpHandler * h):HelpHandler(h){}
             void HandleEvent(Event e)
             {
                 if( can deal with event)
                 {
                     do some thing;
                 }
                 else
                 {
                   HelpHandler::HandleEvent(e); //////交给链的下一个对象
                 }
             }
         };
         class Dialog: public HelpHandler
         {
             Dialog(HelpHandler * h):HelpHandler(h){}
             void HandleEvent(Event e)
             {
                 if( can deal with event)
                 {
                     do some thing;
                 }
                 else
                 {
                   HelpHandler::HandleEvent(e); //////交给链的下一个对象
                 }
             }
         };
        
         app:
         {
             dialog = new Dialog(0); /////他下面没有结点
             button = new Button(&dialog);  //链上他的下一结点
             button->HandEvent(event); ///// event 可能会被dialog 或 button中的一个处理。
         }
  
  
   2. command 命令
  
     使用: 1. command将调用操作的对象,与知道如何实现该操作的对象解耦。(命令的接收者在声明命令对象的时候装配)
              但是这带来的好处是什么呢? 命令作为一个对象可以自己提供execute方法执行自己,省去了响应命令时的switch?或者是
              响应命令的总入口(对象)不需要知道自己管理提供的所有功能了,也不需要自己去区分那个命令由那个功能处理了。
              可能命令接收者仅能提供一些基础功能,而处理每个命令则需要对基础功能进行排序组合来实现,这样可以放到command里面自己去
              实现(使用基础功能)。
           
            2. 把命令作为一个对象处理,他有了一定的声明周期,为回滚提供了可能,只要每个command实现UnExecute方法。
           
            3. 可以将多个命令组合为一个复合命令(组合模式)。
            4. 新增command容易,不影响现有的类。
    
     原理:将command封装成一个类,自己提供Execute方法。 同时自己可能会维护一个命令接收者的引用(在声明命令时注册进去),这是提供具体动作的对象。
           必须考虑: 命令到底达到何种智能程度,是自己把命令全处理完,还是直接转交给命令接收者。 这里有一个度要根据情况把握。
     
           多个命令还可以组合成一个。
    
     实现:
     class Command
     {
         virtual void Execute() = 0;
     };
     class OpenCommand : Command
     {
       OpenCommand(Application * a):__application(_application){}
         void Execute()
         {
             _application->AddDocument();
         }
     private:
         Application *_application;
     };
    
     app:
     {
         Application app; ////命令的接收者
         Command * open = new OpenCommand(&app);
         open->Execute();   
     }
    
    
   3. Interpreter 解释器
  
   解释器和组合类似,实现中一定会用到组合。 他们的不同一点是组合重在通过一种通用的方式表达对象,即便
   是一群对象,表达起来也像是一个对象一样。
  
   解释器是为了解决特定问题,自行设定了一种语音给clientAPP,或用户界面。通过翻译这种语音,确定了解释器树(或列表)
   中的每个对象(解释某一特定语法的子类)。
  
   他和组合模式比首先多了一步翻译语音,将语言翻译成为各个解释器子类(构建语法数)。 并且各个子类的行为也可以不一样,比如字符串查找 “以什么开头,以什么结尾,包含什么。。。”
   #a  以a开头;   $a  以a结尾   %a  包含a。
   则可通过定义三个子类, 并且通过翻译语言 : #ss$b  查找以ss开头以b结尾的字符串.
  
  
   4. Iterator 迭代器
  
   动机: 提供一个方法来遍历一个聚合对象,而又不暴露该对象的内部表示。
   List的iterator就是一个实例, 我们并不知道数据在list中是怎么存储的。
  
   这样还可以支持对同一个聚合对象同时进行多个遍历、不同的遍历。使用不同的迭代器就可以了。
   为遍历聚合提供了统一的接口。
  
   实现:
   template < class Item>
   class Aggregate
   {
       long count();
       Item & getitem(long index); /////实际的聚合对象,他自己提供一个根据下标获取元素的方法,这样iterator就可以不声明为他的友元了。
                                        把迭代器声明为聚合的一个友元,也是一种方法,但是破坏了聚合的封装性。
   };
  
   class Iterator  ////抽象迭代器接口
   {
       virtual void first() = 0;
       virtual void next() = 0;
       virtual bool isDone() const = 0;
       virtual Item CurrentItem() const = 0;
   };
   class ConceretIterator : public Iterator
   {
       ConceretIterator(const Aggregate & a):aggregate(a),_current(0){}
       void first()
       {
           _current = 0;
       }
       void next()
       {
           _current++;
       }
       bool isDone() const
       {
           _current == aggregate.count();
       }
       virtual Item CurrentItem() const
       {
           return aggregate.getitem(_current);
       }
   private:
       long _current;
       Aggregate & aggregate;
   };
   上面是一个外部迭代器,就是对item的操作在外面app中进行,迭代器只是把item返回出去了。
   app:
   {
       ConceretIterator i(Aggregate);
       for(i.first();!i.IsDone();i.next()
       {
           item & item = i.CurrentItem();
       }
   }
  
   还可以在aggregate里面定义工厂,通过工厂方法创建相应的迭代器。★ 因为工厂通常要使用new,所以迭代器也可以再包一个代理,以便迭代器对象的释放。
   即 IteratorProxy(CreateIterate());IteratorProxy本身是迭代器的proxy模式,CreateIterate是工厂模式,里面使用new方法。
   这时需要IteratorProxy重载->和*方法,把包含的对象吐出来。在IteratorProxy的析构里面delete工厂new出来的对象。
  
   还有一种内部迭代器,即把对象的操作自己搞定。
   class ListTraverser{
       ListTraverser(Aggregate & a):iter(a){}
       bool traverse()
       {
           for(iter.first();!iter.IsDone();iter.next()
           {
               ProcessItem( iter.CurrentItem());
           }
       }
   protected :
       virtual bool ProcessItem(const Item &); ///// item的处理函数
   private:
       Iterator & iter;  // 自己持有一个迭代器
   };
  
  
   5. mediator 中介者
  
   意图: 当一组对象以复杂的方式通信,产生的互相依赖关系混乱切难以理解时。
          想定义一个分布在多个类中的行为,又不想生成太多的子类。
          当一个类引用了很多其他对象,导致其复用性下降。
          这时可以通过一个中介者,由中介者来持有和维护一系列对象的交互,协调各个同事对象协作行为。
         
   原理:用一个中介来封装一系列对象,使得各对象不需要显式的互相引用,从而使其松散耦合。
  
   实现:
   class DialogDirector
   {
       void changed() //////中介对外的接口
       {
           _ok.xxx();
           _cancel.xxx();
           if(_fontlist changed)
           {
               xxxx;
           }
       }
   private:
       Button & _ok;   /////////维护几个同事对象,协调他们之间工作
       Button & _cancel;
       ListBox & _fontlist;
   };
  
   6. memento 备忘录
  
   意图: 在不破坏封装性的前提下获取一个对象的内部状态,并将其保存在对象之外,以便以后需要时恢复对象原先保存的状态。
  
   原理: 被保存状态的对象叫原发器。 原发器自己提供接口生成一个自己的备忘录对象,并提供恢复状态接口来装入备忘录。
          即原发器自己定义自己的备忘录,并提供接口返回出来。 应用程序可以拿到这个备忘录,并且在需要时还给原发器去恢复数据。
         
   实现:
  
   class OriginatorMemento  ///备忘录
   {
       ~Originator();
   private:
       firend class Originator;
       OriginatorMemento();
   };
  
   class Originator  ///原发器
   {
       OriginatorMemento * CreateMemento();
       void setMemento(OriginatorMemento & memento);
   };
  
   app
   {
       OriginatorMemento * _state = Originator.CreateMemento();
       /////////////////////////////// ........
      
       Originator.setMemento(_state);/////可通过此方式导入备忘录
   }
  
  
   其实就好像以加密的方式把数据备份一个文件出来一样,怎么备份和使用数据全是原发器的事情,对外完全封装。
  
  
   7. observer 观察者
  
   意图: 定义对象间的一种一对多的关系。 当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并自动做更新。
  
   原理: 观察者的是一个对象,他可以持有被观察对象的引用。 被观察对象提供一个注册方法,把希望观察他的对象们注册到他内部。
          所有的观察者都继承自抽象的接口。
          当被观察者发生改变时,通过自己的notify接口,依次调用注册到他内部的观察者的update方法,并把自己传过去,以此达到观察者们感知他变化的目的。
         
   实现:
  
   class Observer
   {
       virtual void Update(Subject *) = 0;
   };
  
   class ConceretObserver : public Observer
   {
       void Update(Subject * s)
       {
           XXXXXXXXXXXXXXXX;
       }
   };
  
   class Subject
   {
       void Register(Observer * o)
       {
           m_oberList->push_back(o);
       }
       void Notify()
       {
           for(i = m_oberList.begin();  i!= m_oberList.end(); i++)
           {
               (*i)->Update(this);
           }
       }
       void Function()//////////// 目标类某个具体函数。 当里面发生数据改变时,调用Notify触发他的观察者。
       {
           ////////////
           Notify();
       }
   private:
       list<Observer*> m_oberList;
   };
  
   ★ 如果有这种情况:当一个操作涉及多个互相依赖的目标,当这些目标的修改全部完成之后才能通知他们的观察者,这时可以引入一个ChangeManager,负责维护目标和观察者之间的关系和通知时机。
   这种情况下,changeManager是观察者和目标之间的Mediator模式。 changeManager通常用单件来实现。
  
   还有可能,观察者仅希望观察特定事件的改变,这时可以通过注册接口定义他感兴趣的观察事件。可以节省一定的无效通知。
  
   8. state 状态
  
   意图: 允许一个对象在其内部状态改变时改变他的行为。对象看起来就像修改了他的类。
  
   原理: Context定义客户感兴趣的接口,并维护一个具体的状态子类的引用,这个状态子类对应Context的当前状态。
          通过抽象的state定义一个接口,封装与context一个特定状态相关的行为。 然后通过具体状态子类来实现与一个context状态相关的行为。
          context将与状态相关的请求委托给当前的具体state子类。context可以将自己作为参数传递给处理该请求的状态对象,状态对象可以访问context。
          客户程序调用的接口还是context的接口。
          context和具体state子类都可以决定哪个状态是另外一个状态的后继者。
         
    他将与特定状态相关的行为局部化。
   
   
    9. strategy 策略
   
    意图: 定义一系列算法,把他们一个一个封装起来,并且他们可以互相替换。使得算法可以独立于使用他的客户而变化。
           比如可以有很多个文字换行算法,每一个算法封装成一个策略。
           程序在当不同的时候可以用不同的算法。
          
           如果在context里面直接实现算法,则会将context的实现与算法实现混合起来,从而是context难以维护。并且算法是硬编码的,不能动态改变算法。
           如果在这种情况下派生context,则会得到一堆相关的类,而唯一的区别就是算法。
          
           这样不如将算法的实现独立于context,使得context可以切换他使用的算法。
          
   
    实现:可以用strategy作为类模版参数来配置类,但这样可以实现在编译时选择strategy,无法在运行时实现选择strategy。
    另一种方式:
   
    class Context
    {
        Context( strategy * s ):_s(s){}
       
        void dosth()////////// 应用程序实现某个功能
        {
            _s->compose(); /////////调用算法的抽象接口
        }
    private:
        strategy * _s;
    };
   
    class strategy
    {
        virtual void compose() = 0;
    };
   
    class conceretStrategy1 : public strategy
    {
        void compose()
        {
            XXXXXXXXXXXXXX;
        }
    };
   
    class conceretStrategy2 : public strategy
    {
        void compose()
        {
            XXXXXXXXXXXXXX;
        }
    };
   
    app
    {
        Context * s1Context = new Context( new conceretStrategy1);
        Context * s2Context = new Context( new conceretStrategy2);
       
        s1Context->compose();
    }
   
    10. template method 模版方法
   
    意图: 定义一个操作中的算法的骨架,并将一些步骤延迟到子类中去。 使得子类可以不改变算法的结构即可重定义算法的某些特定步骤(或具体操作)。
   
    一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
   
    实现: 一个模版方法调用的原语操作可以定义为protected,保证只被模版方法调用。 必须重定义的原语操作定义为纯虚函数。
           模版方法自身不需要被重定义,因此定义为非虚函数。
          
    class AbstractClass
    {
        void TemplateMethod()  //////////实现一个算法的骨架,里面使用可被重载的方法DoOperate(), 并在他的子类中重载; 子类使用父类的算法骨架,重定义自身的具体操作。
        {
            XXXXXXXX;
            DoOperate1();
            XXXXXXXXXXXXX;
            DoOperate2();
            xxxxxxxxxxx;
        }
        virtual void DoOperate1() = 0;
        virtual void DoOperate2() = 0;
    };      
  
    class ConceretClass : public AbstractClass
    {
        virtual void DoOperate1()
        {
            XXXXXXXXXXXXXXXX;
        }
        virtual void DoOperate2()
        {
            XXXXXXXXXXX;
        }
    };
    模版方法基于继承技术,不如strategy灵活。
   
    11. visitor 访问者
   
    意图: 一个作用于某对象结构中的各元素的操作, 可以在不改变各元素类的前提下定义对这些元素的新的操作。
           需要对一个对象结构中的对象进行很多不相关的操作,而不想这些操作互相干扰。
          
           访问者使得增加依赖于负责对象的操作变得容易。仅需增加一个新的访问者几个在对象结构上定义一个新操作。
           访问者可以集中相关的操作而分离无关的操作。使得相关的操作行为集中在访问者中而不是分布在对象结构中。
           增加新的操作变得容易。
          
           他强调的是操作的多样和独立。
          
     原理: 定义一个抽象的visitor,里面确定一个访问对象元素的接口。
            用具体的conceretVisitor继承自visitor。
            用抽象元素定一个接受访问者的接口,比如Accept操作,以一个访问者为参数。
            具体的元素继承自抽象元素,实现accept操作。
           
     实现:
     class Equipment
     {
         virtual int discountPrice();
         virtual accept(EquipmentVisitor * v);
     };
    
     class FloppyDisk : Equipment
     {
         accept(EquipmentVisitor * v)
         {
             v.visitFloppyDisk(this); /////////////每一个具体的Element对象的accept方法实现都不同,使用的是visitor中不同的方法,但是这些方法都是被重载的、有统一接口原型的。即
                                                   每个具体的Element对象都可以有多个visitor,他的多个visitor有共同的抽象接口。
         }
     }
     class EquipmentVisitor
     {
         virtual void visitFloppyDisk(FloppyDisk * f);
         virtual void visitBus(Bus * b);
     };
    
     class InventoryVisitor: public EquipmentVisitor
     {
         void visitFloppyDisk(FloppyDisk * f)
         {
             f->Accumulate(); ////////计算FloppyDisk的存货数量
         }
     };
    
     ★★★★★ GoF的《设计模式》5.12节讲的非常重要,要多看几遍。
0 0
原创粉丝点击