zoukankan      html  css  js  c++  java
  • Design Pattern

    策略模式

    策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
    场景, 对于输入, 在不同的情况下有不同的处理逻辑, 即有不用的算法
    那么c的做法, 把各个算法封装成函数,仍然用大量的if... else...来判断, 条件不同使用不同的算法函数来处理.
    面向对象的做法, 上面提到了工厂模式, 建个抽象算法基类, 然后把每个算法抽象为具体的算法类. 然后用户代码根据不同的情况, 利用工厂类创建不同的算法类来处理, 如下,
    Strategy s = StrategyFactory.CreateStrategy(type)
    s.AlgorithmInterface() 
    这样做其实没有问题, 我觉得挺好, 但如果进一步提高封装性, 不想让用户知道有具体的Strategy, StrategyFactory存在, 那就要用到策略模式
    使用Context类来替换Factory类, 好处是Context类不直接把Strategy对象返回给用户, 而仅仅提供统一的算法接口给用户, 做法如下,
    Context c = new Context(type)  //Context的构造函数中会根据type创建相应的Strategy 类
    c.ContextInterface()                   //ContextInterface封装并调用s.AlgorithmInterface() 
    其实就是增加了一层封装, 这样用户代码只需要知道Context类, 而不需要关心其他细节. 这应该是迪米特法则的应用, 不该知道的就别知道, 解耦合.
    个人认为策略模式的意义并不大, 只是提高些封装性.

    image

    原型模式

    原型模式(Prototype Pattern), 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.
    原型模式其实就是从一个对象再创建另外一个可定制的对象, 而且不需知道任何对象创建的细节, 因为就是完全拷贝原型对象, 所以原型对象是怎么创建的不用管.

    class ConcretePrototype : Prototype 
    { 
        Prototype Clone() //提供Clone函数 
        { 
            return this.MemberwiseClone() //memberwise是属于浅拷贝, 此处clone的逻辑按需更改, 这儿只是个例子 
        } 
    } 

    用户使用时,

    ConcretePrototype p1 = new ConcretePrototype () 
    ConcretePrototype p2 = p1.Clone() //不用重新构建ConcretePrototype对象, 通过clone就可以得到新的对象

    image

    原型模式的好处,
    当对象构造函数执行时间较长时, 只用调用构造函数一次, 通过Clone就可以创建多个对象, 大大提高效率,
    可以隐藏对象创建细节,
    可以动态获得对象运行时状态.

    模板方法模式

    模板方法(Template Method)模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
    这是一个很简单, 却被非常广泛使用的模式, 只要使用过继承和多态, 或多或少都会使用到模板方法, 只是你不一定意识到.
    场景, 需要设计一系列的算法或功能, 他们在主体框架上是相同的, 但是算法细节却不同
    在基类的TemplateMethod中定义该系列算法的通用主体框架, 在涉及到易变化的细节时, 把细节抽象成虚函数并调用, 如PrimitiveOperation.
    这样在基类中, 这些细节都是没有定义的, 你必须在子类中来override这些虚函数来改变算法细节.
    这就是上面定义说的, 在基类中定义算法骨架, 而将一些易变的步骤延迟到子类中override.

     image
    模板方法模式的好处,
    通过把通用的不变的行为放在基类, 从而去除子类中的重复代码, 以利于代码复用.
    其实这个是个普遍的想法, 你不知道这个模式, 在写代码的时候也会这么写的.

     

    建造者模式

    建造者模式(Builder Pattern), 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
    场景, 需要通过若干步骤创建一组复杂对象, 并且对于不同类型对象, 各个步骤中的细节会不相同.  
    例子, 肯德基做汉堡, 比较复杂, 有一个固定流程, 比如准备原料, 放调味, 加热, 包装. 但是不同的汉堡, 各个制作步骤的细节是不一样的. 比如调味换一下就有不同风味的汉堡. 
    普通的做法, 对每一种复杂对象单独写一个创建函数, 这样也可以, 但是有些问题
    制作方法的主要步骤是一致的, 只是各个步骤细节不同, 所以会有大量的代码重复
    因为创建步骤比较复杂, 写某个创建函数时会由于疏忽, 遗漏步骤或写错步骤顺序

    对于这种问题, 明显是模板方法模式的思路...
    把通用的流程步骤抽象到基类中, 在派生类中重写具体步骤函数
    如果基于模板方法模式, 就直接把Construct()放在Builder类, 不需要多出一个Director类
    个人觉得这样也挺好, 如果Construct()逻辑是固定的话

    建造者模式抽象出Director, 会更加灵活, 如果可以通过Builder中不同的步骤组合产生出不同的产品, 即如果construct的逻辑可以变化
    个人觉得建造者模式就是模板方法模式的一种基于对象创建场景的简单变化, 多出的那个Director, 个人觉得可有可无......也许是我不能理会建造者的精髓.......

    image

    class Product 
    { 
        List parts = new List(); 
        void add(part) {parts.add(part)} 
    }
    
    abstract class Builder 
    { 
        void BuildPartA(); 
        void BuildPartB(); 
    }
    
    class ConcreteBuilder : Builder 
    { 
        Produce p = new Product(); 
        void BuildPartA() {p.add('a')} 
        void BuildPartB() {p.add('b')} 
    }
    
    class Director 
    { 
        void Constract (Builder builder) 
        { 
            builder.BuildPartA(); 
            builder.BuildPartB(); 
        } 
    }

    客户代码, 不需要知道具体的建造过程, 只要将相应的builder传给director就可以得到相应的对象.

    Director d = new Director () 
    Builder b = new ConcreteBuilder () 
    d.Construct(b)

    观察者模式

    观察者模式(Observer)又称发布-订阅模式(Publish/Subscribe), 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象. 这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。此种模式通常被用来实现异步事件处理系统.

    场景, 多个类对象间存在一对多的依赖关系, 当某个对象发生变化时需要调用其他对象的函数.
    简单的做法是直接调用, 对象间的直接调用, 方便是方便, 不过最大的问题是对象间的紧耦合. 紧耦合是设计的大忌, 会导致难于扩展和重用.
    所以就需要用观察者模式来抽象对象间的依赖关系, 从而打破对象间的耦合. 而打破耦合的思路就是依赖倒转原则, 对象间依赖于抽象接口而不依赖于具体实现, 这就是观察者模式的核心, 该模式就是依赖倒转原则思路的体现.
    image

    class ConcreteSubject 
    { 
        string subjectState; //主题的状态 
        List observers = new List(); //已注册的观察者的列表
    
        void attach(Observers o) //注册观察者 
        { 
            observers.append(o) 
        }
    
        void detach(Observers o) //删除观察者
    
        void notify() //通知观察者 
        { 
            for o in observers{ o.update()} 
        } 
    }
    
    class ConcreteObserver 
    { 
        public ConcreteObserver(ConcreteSubject sub, string name) //在观察者对象中保存主题对象的引用, 以方便获取主题状态 
        void update(){} //针对主题变化所需要采取的update 
    }

    客户代码,

    ConcreteSubject s = new ConcreteSubject(); 
    s.attach(new ConcreteObserver(s, 'observerA')) 
    s.SubjectState = 'ABC' 
    s.notify() //调用各个注册对象的update接口

    可见模式确实依赖抽象接口, 达到了解耦合的目的, 主题对象既不需要知道(hardcode)有哪些观察者对象, 也不需要知道观察者对象具体的实现接口.
    观察者对象可以通过attach, detach接口来注册和删除自己, 并需要实现update接口供主题类调用.

    这段代码在ConcreteObserver保留了ConcreteSubject 引用, 这样增加了耦合性, 好处是当主题的状态比较多比较复杂时, 而不同的观察者又需要获取不同的主题状态, 为了便于统一处理干脆保存ConcreteSubject 引用, 这样不同的观察者可以各取所需. 个人觉得这种方法不好, 不但增加对象间耦合性, 在设计上也缺乏自然简单.
    对于大多数情况, 只需要在update中, 把变化的状态作为参数传入即可, 无需额外保留ConcreteSubject 引用.

    其实notify函数可以自动被调用, 将notify封装在相应的状态更新接口中, 对于一组状态甚至可以为不同的状态变化封装不同的notify函数.

    这个模式缺点是, 每个观察者必须实现update接口
    如果某个观察者无法增加update接口怎么办? 简单的办法就是adapter模式, 写个适配类来封装它, 并在适配类中实现update接口, 这样的问题适配类和该观察者耦合.
    .net的也委托可以解决这个问题,
    public delegate void NotifyEventHandler(object);
    NotifyEventHandler NotifyEvent;
    NotifyEvent += new NotifyEventHandler(observer.old_func());
    但其实一样, 在往委托NotifyEvent里面加observer.old_func()本身也产生了耦合.

    对于异步事件处理系统, 如python中的Twisted, 就是该模式的最好的应用. reactor不断侦听端口获取事件, 对特定的事件调用事先注册过的callback函数.
    reactor 就是主题对象, 而封装了callback的类就是观察者对象, 只是这里观察者对象的接口不止update一个, 除了初始的事件所对应的callback接口(如, read, connectionlost, timeout)外, 开发者还可以自定义, 只是需要在reactor中注册新的事件. 至于是使用一个抽象接口, 还是多个抽象接口, 本质是一样的, 总之, 使对象间依赖于抽象接口就可以解耦合.

    状态模式

    状态模式(State), 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
    这个模式典型的应用是状态机, 场景, 一个对象有多种状态, 不同的状态下行为是不一样的, 这个在现实中也很多, 一个人他心情不同, 你和他打招呼, 他说的话也是不同的
    这个简单实现就用大量的条件判断, 判断出不同的状态, 执行不同的逻辑. 这样的实现对于状态比较多的情况, 明显不好维护, 无法重用.
    这种大量条件判断语句的情况, 在工厂方法和策略模式中都碰到过, 解决的办法就是把每个条件语句抽象成子类, 这样便于扩展和修改. 这儿同样的思路......
    image

    class ConcreteStateA : State 
    { 
        void handle () 
        { 
            doSomethingA() // 在状态A下需要做的事 
            if (......)  //在满足特定条件下, 需要由状态A切换到状态B, 切换后context表现出的就是状态B的行为 
            { 
                context.State = new ConcreteStateB(); 
           } 
        } 
    }
    
    class ConcreteStateB : State 
    { 
        void handle () 
        { 
            doSomethingB() 
            if (......) 
            { 
                context.State = new ConcreteStateB(); 
            } 
            else if (......) //可以切换到不同状态 
            { 
                context.State = new ConcreteStateC(); 
           } 
        } 
    }
    
    class Context 
    { 
        State state; 
        public Context(State state) {this.state = state} //定义初始状态 
         public void Request() 
        { 
            state.Handle(this); 
        } 
    }

    客户代码,
    Context c = new Context(new ConcreteStateA());
    c.request();

    这段代码本来在Context.Request中是会有大段的条件判断来决定执行哪段逻辑, 而现在他只管调用state.Handle(this), 具体什么逻辑, 由具体的state对象决定.
    而state间切换的条件判断也分散到各个子类中, 增加了可读性和可维护性. 这个模式适用的场景还是很清晰的, 就是可以使用状态机的地方.

    备忘录模式

    备忘录模式(Memento), 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
    场景, 最常见的是游戏存档, 文档操作为了undo而做的备份, 总之需要对对象中的某些数据进行备份, 以供后面恢复.
    大家是不是想到可以使用原型模式来解决这个问题, 没错, 通过clone可以保存对象镜像, 但是这样做有两个问题,
    原型模式必须clone整个对象, 不能只备份对象中部分重要数据
    clone的对象中除了数据也包含了所以的接口, 在备份中暴露原有对象接口也是不适合的 
    所以就需要备忘录模式, 可用于仅仅部分备份对象数据, 这个模式也挺简单,
    Memento类, 备份类用来封装所有需要备份的数据
    Originator类, 待备份类, 通过CreateMemento场景备份类对象, 并通过SetMemento来恢复
    Caretaker类,  通过CreateMemento场景备份的类对象放哪儿? 把他暂存到Caretaker类中, 这个抽象类负责save/load备份类, 至于是存到磁盘还是直接放在内存对可以透明.

    image

    class Originator 
    { 
        string state1; //需要备份的若干数据 
        string staten;
    
        public Memento CreateMemento() 
        { 
            return(new Memento(state1,.....staten))  //创建备份对象 
        }
    
        public SetMemento(Memento m) //恢复备份对象 
        { 
            state1 = m.state1 
            staten = m.staten 
        } 
    }
    
    class Memento 
    { 
        string state1; 
        string staten;
    
        public Memento(string state1,...staten) 
    }
    
    class Caretaker //用于保存和读取备份对象 
    { 
        Memento m; 
        void save(m); 
        void load(m); 
    }

    组合模式

    组合模式(Composite), 将对象组合成树形结构以表示‘部分整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
    Composite模式采用树形结构来实现普遍存在的对象容器,使得客户代码可以一致处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
    核心的想法就是将“客户代码与复杂的对象容器结构”解耦.
    无论对于容器还是叶子对象, 都具有相同的接口, 客户代码无需关心这些对象的内部实现, 甚至不用关心是容器对象还是叶子对象, 都可以用相同的接口相同的方式取处理, 从而满足依赖倒转原则
    典型的应用就是文件系统, 文件系统由目录和文件组成。每个目录都可以装内容。目录的内容可以是文件,也可以是目录。

    Composite模式中有个需要思考的问题是“透明性”和“安全性”的两难问题,
    透明性, 是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,这样无论leaf和composite接口上完全一致, 不用区分, 满足了透明性, 但这样在叶子对象中多了些不应该的接口. 
    安全性, 对象容器相关的方法仅仅定义在“表示对象容器的Composite类”中, 这样调用时, 需要区分容器和叶子对象破坏了透明性, 这样的好处是没有在叶子里面添加不需要的接口.

    image

    class Component 
    { 
        void Add (Component c); 
        void Remove (Component c); 
        void Display (Component c); 
    }
    
    class Leaf : Component 
    { 
        void Add (Component c) {print 'Leaf don't have add interface'} //透明模式 ,所有component具有相同的接口, 这是Leaf中相应接口失效 
        void Remove (Component c) {print 'Leaf don't have remove interface'} 
        void Display (Component c) {} 
    }
    
    class Composite : Component 
    { 
        List children = new List ()
    
        void Add (Component c) {children.Add(c)}  
        void Remove (Component c) {children.Remove(c)} 
        void Display (Component c) {} 
    }

    迭代模式

    迭代器模式 (Iterator), 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

    这个模式用的相当普遍, 代码最常碰到的情况就是遍历一个聚合对象, 一个list.
    这个模式和组合模式的思路一样的, 核心思路就是解耦, 迭代模式就是把客户代码和聚合对象的内部实现, 以及聚合对象的遍历方法经行解耦.

    对于任意聚合对象的遍历只依赖于First, next, isDone接口, 无论聚合对象内部的实现或遍历的方法, 符合依赖倒置原则.

     image

    class ConcreteIterator : Iterator
    {
        private ConcreteAggregate aggregate
        private int current = 0;
        
        public ConcreteIterator(ConcreteAggregate aggregate)
        {
            this.aggregate = aggregate //需要被遍历的聚合对象
        }
        
        //下面三个接口的实现, 完全取决于遍历算法和聚合对象的内部实现
        //遍历算法可以随意变化, 正向遍历, 反向遍历, 随机遍历
        //聚合对象的实现, 可以是list, 可以是数组...等等
        //但是用户总是可以通过First, Next,isDone遍历所有item
        public override object First()
        {
            return aggregate[0];
        }
        
        public override object Next()
        {
            current++;
            return aggregate[current];
        } 
        
        public override object isDone()
        {
            return current >= aggregate.Count ? true : false ;
        }
    }   
    
    class ConcreteAggregate : Aggregate
    {
        List<object> items = new List<object>();
        
        public override Iterator CreateIterator() //在聚合对象上创建迭代器
        {
            return new ConcreteIterator(this);
        }
    }

    客户代码,

    ConcreteAggregate a = new ConcreteAggregate();
    Iterator i = new CreateIterator(a);
    
    object item = i.First();
    while (!i.isDone())
    {
        i.Next();
    }

    桥接模式

    桥接模式 (Bridge), 将抽象部分与它的实现部分分离, 使它们都可以独立地变化, 减少它们之间的耦合.
    桥接模式的本质是通过聚合来替代继承以达到解耦合的目的(合成/聚合复用原则).

    如下图, Abstraction和Implementor之间也可以使用继承, 这样implementor就和Abstraction紧耦合, 任何Abstraction的变动都会影响 implementor. 但其实Abstraction和Implementor之间并没有严格的'is-a'关系, 完全可以独立的变化, 这里用聚合来替代继承以达到解耦合的目的.

    举个例子, Abstraction就是手机, Implementor是电话簿功能

    基于继承的design就是, Implementor继承手机类, 成为具有电话簿功能的手机类.
    这样设计也可以, 但是两者通过继承紧耦合, 任何手机类的改动都会影响到他的子类.
    并且当手机类, 具有电话簿功能的手机类进行扩展时, 非常不灵活
    如手机有多个品牌, 扩展为Nokia手机, Moto手机, Samsung手机
    电话簿又不同种类, 扩展为支持SIM的电话簿, 支持头像的电话簿, 支持开心网的电话簿
    用继承来设计的话, 就需要3*3=9个子类, Nokia支持SIM的电话簿手机, Moto支持SIM的电话簿手机……

    其实手机和电话簿功能并没有是'is-a'关系, 而是'has-a'关系, 所以根据合成/聚合复用原则, 这儿应该使用聚合代替继承

    于是如下图, 手机和电话簿功能用聚合关系来切断了紧耦合
    手机和电话簿可以独立的扩展变化, 仅仅的联系就是一个品牌的手机需要用某一个电话簿就聚合过来, 并可以随意的切换各种功能的电话簿.

    这个模式大大显示了合成/聚合复用原则的重要性, 并看到滥用继承所带来的不良的后果.

     image

    class ConcreteImplementor : Implementor
    {
        public override void Operation()
        {
            //具体的实现...
        }
    }
    
    class Abstraction
    {
        protected Implemetor implementor; //将具体实现分离到Implemetor类中, 在Abstraction中不涉及任何实现
        
        public void SetImplementor(Implemetor implementor) //可自由的切换实现
        {
            this.implementor = implementor;
        }
        
        public virtual void Operation()
        {
            implementor.Operation() //接口调用具体实现
        }
    }
    
    //客户代码
    Abstraction ab = new Abstraction();
    ab.SetImplementor(new ConcreteImplementor());
    ab.Operation();
     
    同时在com本质论中谈封装性的时候, 通过桥接模式, 把接口和实现分离开, 这个应该是桥接模式更原本的应用场景,
    class __declspec(dllexport) FastString {
        const int m_cch; //新增加的成员变量
        char *m_psz;
    public:
        FastString(const char *psz);
        ~FastString(void);
        int Length(void) const;
        int Find(const char *psz) const;
    } ;

    对于上面的dll, 每当这个FastString增加或减少成员变量时, 客户程序必须也重新编译.

    原因就是这个类把实现暴露给了客户, 客户在创建对象时, 必须根据成员去分配空间, 所以当成员变化了, 分配的空间必须更改, 否则就会异常.

    做法就是使用桥接模式, 把实现分离出来, 只让客户看到不变的接口类, 而具体的实现对客户透明.

    //faststringitf.h, 声明抽象接口类
    class __declspec(dllexport) FastStringltf {
        class FastString;
        FastString *m_pThis; //类对象指针,大小恒定
    
    public:
        FastStringltf(const char *psz);
        ~FastStringltf(void);
        int Length(void) const;
        int Find(const char *psz) const;
    } ;
    
    //faststringitf.cpp
    #include "faststring.h"
    #include "faststringitf.h"
    FastStringltf::FastStringltf(const char *psz)
    :m_pThis(new FastString(psz)) { //在接口类中new FastString, 对客户透明, 客户看到的只是不变的接口类
        assert(m_pThis != 0);
    }
    FastStringltf: :~FastStringltf(void) {
        delete m_pThis;
    }
    int FastStringltf::Lengthltf(void) const {
        return m_pThis->Length();
    }
    int FastStringltf::Find(const char *psz) const {
        return m_pThis->Find(psz);
    }

    桥接模式比较难于领会, 总结一下, 桥接模式就是把继承体系中可以独立变化的部分(符合'has-a'关系的部分)分离出来, 形成新的独立的继承体系, 当需要使用该部分功能时, 通过聚集把需要功能类拿来使用即可. 做到招之即来, 挥之即去, 不带一点耦合.

     

    职责链模式

    职责链模式(Chain of Responsibility), 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
    场景, 常用的场景是领导审批请求, 或消息处理机制
    最不加思考的实现就是使用大量的条件语句进行判断, 满足什么样的条件就把请求发给什么handler去处理
    这样的缺点是显而易见的, 处理的流程用条件语句hard code写死, 如果流程要改变或扩展都很不方便
    这个模式的实现和思路都和状态模式很像, 把各个handler封装为类, 把大量的条件语句分散到各个类中去, 不同的是类与类之间的关联调用形成职责链, 而状态模式, 通过切换context类的状态对象形成状态机.

    职责链模式切断了请求者和整个处理流程的耦合, 请求者只需知道直接领导是谁, 并向他提出请求, 而不需要管后续的处理流程. 而对于每一级处理者, 也是只知道下一级处理者, 而不用关系整个处理流程. 这样最大的好处就是修改和扩展职责链非常方便.

     image

    abstract class Handler
    {
        protected Handler successor;
        //对于handler而言, 他只知道他的后继是谁, 而不用了解整个处理过程, 达到解耦合的效果
        //即只知道他的直接领导, 自己解决不了就发给他, 至于他怎么解决或是找谁解决, 我不知道也不用关心
        public void SetSuccessor (Handler suc)
        {
            this.successor = suc; 
        }
        
        public abstract void HandleRequest(int request);
    }
    
    class ConcreteHandler1 : Handler
    {
        public void HandleRequest(request)
        {
            if (valid(request))//当request满足条件, 即可以被当前handler处理时
            {
                //处理reuqest
            }
            else if (successor != null)
            {
                successor.HandleRequest(request); //否则把request发给后继处理
            }
        }
    }
    
    //同样逻辑可以声明多个handler
    class ConcreteHandler2 : Handler
    {
    }
    
    class ConcreteHandler3 : Handler
    {
    }
    
    //客户代码
    //先建立职责链
    Handler h1 = new ConcreteHandler1();
    Handler h2 = new ConcreteHandler2();
    Handler h3 = new ConcreteHandler3();
    h1.SetSuccessor(h2)
    h2.SetSuccessor(h3)
    
    //客户只需将请求提交给h1, 真正的处理流程对他透明
    h1.HandleRequest(request);

    享元模式

    享元模式(Flyweight), 运用共享技术有效地支持大量细粒度的对象。

    享元模式可以避免大量非常相似类的开销. 在程序设计中有时需要生成大量细粒度的类实例来表示数据, 如果这些类实例都是相同的, 或者可以简单的把不同通过参数抽象出来, 那么生成这么多相同的类对象, 大大的增加了内存耗费. 合理的做法就是只生成一个实例, 下次直接返回该实例的引用就可以了, 不用反复的生成实例.
    这个模式的应用,
    在python中一切皆是对象, 包括象a, b, c这样的字符也是对象, 而在平常会大量的使用这样的英文字符, 如果每次使用都生成实例, 会浪费很多内存, 所以采用享元模式.
    在线游戏, 围棋, 需要大量的棋子, 而且多人在线游戏, 如果为每个棋子都生成实例的话, 会大大限制在线人数. 所以采用享元模式, 只需要生成黑棋, 白棋两个实例, 然后在外部保留各自棋子的位置即可.  
    image

    解释器模式

    解释器(Interpreter), 给定一个语言, 定义它的文法的一种表示, 并定义一个解释器, 这个解释器使用该表示来解释语言中的句子.
    这个模式如其名, 就是用进行简单的翻译, 解释工作的. 可以定义一些Terminal语法规则和Nonterminal语法规则, 并依照定义的语法规则来进行解释翻译.
    如下图, 每个语法规则都是抽象为一个子类, 所以这个模式的特点是便于语法规则的更新和扩展, 只需要通过修改和增加相应的子类. 
    这个模式的不足, 要为每一条规则定义一个类, 所以当文法规则非常复杂时, 就难以管理和维护, 需要使用其他更高级的技术, 如编译器.

     image

    public class TerminalExpression:AbstractExpression
    {
        public override void Interpret(Context context)
        {
            //定义的Terminal语法规则,一般都是简单的匹配替换规则
            //如, ("壹", 1), ("贰", 2), ("参", 3)
        }
    }
    
    public class NonterminalExpression:AbstractExpression
    {
        public override void Interpret(Context context)
        {
            //对于比较复杂的解释器, 需要定义非Terminal语法规则
            //因为待翻译语句无法直接用Terminal语法规则解释, 所以需要用非Terminal语法规则进行分解
        }
    }
    
    //客户代码
    Context context = new Context();
    ArrayList list = new ArrayList();
    
    //填充语法规则,可以根据你的需要决定各个语法规则的先后次序
    list.Add(new TerminalExpression());
    list.Add(new NonterminalExpression());
    list.Add(new TerminalExpression());
    list.Add(new TerminalExpression());
    
    //逐条用语法规则解析
    foreach (AbstractExpression abstractExpression in list)
    {
        abstractExpression.Interpret(context);
    }

    总结,

    GOF把所有模式分为3类, 创建型, 结构型, 行为型, 有一定道理, 不过个人觉得这只是根据表象在分类, 其实对帮助理解模式意义不大, 我个人对各个模式重新依据我的理解分个类.

    选择型
    工厂, 选择需要创建哪一个对象, 根据不同的参数.
    策略, 选择需要哪一种策略, 和工厂的唯一不同是, 不直接返回策略对象给客户, 而是通过Context提供一层封装.
    状态, 选择哪一种状态, 区别是客户只需给出初始化状态, 状态的选择逻辑分散到各个状态类中, 并自动进行状态选择和切换.
    职责链, 选择谁应该负责, 和状态模式一样, 选择逻辑分散到各个handler中, 客户只需要根据情况选择直接handler, 每一级handler也相似.

    对象创建,复制型
    原型, 通过拷贝现有对象创建新对象
    备忘录, 备份对象部分数据, 和原型比更灵活
    单例, 保证一个类只有一个实例, 提供全局访问点
    享元, 为了节省空间, 相同的对象只创建一次, 建立对象池用于对象重用. 这个和单例不同的, 对于享元一个类可以创建许多实例(参数不同), 只会重用完全一致的对象.

    反继承型
    装饰, 通常通过继承, 并override父类函数, 实现功能扩展. 但当扩展的非核心功能而是装饰功能时, 不需要使用继承, 而用装饰, 杀鸡别用牛刀.
    组合, 典型'has-a'关系, 应该用组合和聚合来实现, 比如公司和部门, 文档和句子. 并且对于整体和部分实现相同的接口, 实现一致性访问.
    桥接, 当没有绝对的'is-a'关系, 而是'has-a'关系时, 不要使用继承, 用聚合来联接达到解耦的效果.

    中间层型
    代理, 通过代理间接访问
    门面, 通过统一接口来屏蔽子系统的复杂接口和具体实现
    适配器, 系统整合时, 通过适配来把现有子系统的接口统一成标准接口
    中介者, 禁止各个模块间复杂的直接通信, 一切交互都通过中介者来进行

    解耦合,行为型
    观察者,
    迭代,
    命令,
    访问者,

    模板,
    建造者,
    解释器,

  • 相关阅读:
    SVN 部署(基于 Linux)
    禅道部署(基于 Linux)
    MySQL 中文乱码问题
    设置 Linux 支持中文
    SQL 文件导入数据库
    虚拟 IP 设为静态 IP
    python selenium(定位方法)
    python selenium(环境搭建)
    「杂谈」苏州人不能太膨胀
    「SAP技术」如何看Z移动类型是复制哪个标准移动类型而创建的?
  • 原文地址:https://www.cnblogs.com/fxjwind/p/2098875.html
Copyright © 2011-2022 走看看