Adapter Façade Decorator设计模式在分类上属于结构模式。结构模式描述怎样将类和对象结合起来形成一个更大的结构。

            将类和对象结合起来形成一个更大的结构,这里就有一个耦合的问题,如果类和对象是非常稳定的,耦合到什么程度都是没有问题的。问题还是归结到变化上,如果发生变化强耦合的后果就是修改压力会沿着依赖链条传递下去,就像多米诺骨牌一样引起大范围的坍塌。

          从结构模式的描述中我们可以看出耦合是难以避免的,我们要做的就是在这个必然的前提下做出努力,EVP原则(封装变化原则)提示我们封装变化隔离变化,目标是:灵活的耦合;

           什么样的耦合是灵活的耦合?我们经常这样做:耦合发生在两个具体类(可以实例化)之间,一个类持有另一个类的直接引用,这就是具体耦合(Concrete Coupling)。显然这中情况下 类和类之间的依赖是很强的,变化发生波及的范围也是最广的。灵活的耦合就是抽象耦合(Abstract Coupling),抽象耦合中耦合关系发生在一个具体类和一个抽象之间(抽象类或者接口)。

           仔细观察抽象耦合,我们看到了DIP(依赖倒置)原则,依赖倒置原则告诉我们应该依赖于抽象,应该面向接口编程而非实现。DIP强调的就是系统内实体之间的关系灵活性。DIP不是孤立的,应该想到OCPDIP的目标,LSPDIP的基础,DIP关键在抽象。
          结构模式中的耦合是抽象耦合,而且从定义的描述中我们知道还要处理类的灵活耦合和对象的灵活耦合。怎么做到的呢?

         类的结构模式是使用继承机制做到,继承也是封装变化的方法,当一个类从父类继承并实现某一个接口时,这个新的类就把父类的结构和接口的结构结合起来,类的结构模式是静态的。       

         对象的结构模式是使用组合机制来实现,是动态的。

         下面我们以Adapter模式为例,进行分析:

    Adapter

         

          应用Adapter模式的动机就是转化不兼容的接口。

         Adapter模式最常见的用途就是保持多态性。在各种设计模式组合起来使用的时候,Adapter常用来保持其它设计模式所需要的多态。有了Adapter模式我们进行设计时就不必担心原有类的接口了:只要一个类在概念上完成了所需的功能,那么我们就可以使用Adapter模式为它提供正确的接口。

          对象适配器和类适配器的实现使用不同的机制:前者使用组合后者使用继承。

            类适配器使用继承,概念上的适配器和被适配者实际上是一个类,这个类可以方便的使用父类已经实现的方法并进行覆盖改写(override)。同样由于类适配器使用了继承,所以只能使用特定的被适配类,适配器类Adapter和特定的被适配器类Adaptee绑定,所以这里就没有对象适配器灵活了。如果要适配目标类的所有子类,那么类适配器就不能胜任了。

           我们来看一下对象适配器:

            对象适配器使用对象组合,根据LSP(里氏替换)原则,对象适配器可以适配被适配类及其子类。对象适配器将工作委托给被适配者,Client和接口绑定而不是具体实现,它面向接口而非实现,很好的符合了DIP(依赖倒置原则)
        

           Adapter模式实现的工作量是由接口的规模决定的,一个巨大的接口上实现Adapter也是一件痛苦的事情。我们期待一个良好的接口,接口设计的原则最重要的就是ISP原则(接口隔离原则)

          再一次描述一下ISP原则:多个专门的接口比唯一的总接口要好。

          接口的设计问题最容易由接口使用者发现。举一个生活中的例子,无论你使用移动还是联通,定制的服务都是“套餐”,套餐里面好多服务都是必选的。对于很多不会使用短信的群体(比如老年人),短信包月的必选服务就很霸王,对于消费者是一个负担。一个肥胖的接口同样对于使用者也是负担。

         ISP原则拒绝向Client提供不需要的行为,一个类对另外一个类的依赖是建立在最小的接口上。这其实符合另外一个OO设计原则:

         Least Knowledge Principle(LKP) 最少知识原则,另外一个名称是Law of Demeter Lod迪米特法则是指:一个对象应当对其他的对象有最少的了解。如果其中一个类需要调用另外一个类的某一个方法的话,可以通过第三者来转发这个调用。这个第三者实际上是一个包装类,它的作用就是调用转发(Call Forwarding)完成沟通。

    如果要找一个符合Lod原则的典型,那就是Facade模式了:

    Facade

         Facade外观模式定义一个高层接口使得子系统更容易使用。我们使用外观模式的动机是简化。

          引入Facade将子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。 当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过Facade进行通讯,从而简化了它们之间的依赖关系。
            虽然Facade并没有“屏蔽子系统”--子系统的完整接口还是暴露出来了。我们可以强制Facade成为Client与子系统交互的唯一入口,一方面它完成了Client与系统的解耦,另一方面它所处的特殊位置可以做一写跟踪工作,比如特定方法的调用。

    我个人认为这种强制是有必要的,它保证了Facade更严格得遵守了Lod原则。

        甚至我们可以用抽象类实现Facade而它的具体子类对应于不同的子系统实现,这可以进一步降低客户与子系统的耦合度。

         

    Decorator

    装饰者使命是添加行为,这也是我们使用Decorator模式的动机。

         适配器的作用是接口转换,Decorator装饰者模式绝对不允许修改接口,如果原来的对象接口发生变化,它所有的装饰类都要修改匹配它的变化。

        为什么不允许Decorator修改接口?我们要保证已经正常的程序不受到影响,我们做的只是扩展!如果使用继承派生子类会影响对象的内部,而一个Decorator指挥影响对象的外表。Decorator动态的给对象增加新的职责而又不影响其他对象。

     

     简单总结:

  1. 继承也是封装变化的方法
  2. 抽象耦合是灵活的耦合
  3. 接口设计要符合ISP原则
  4. Facade很好的符合了LKP原则
  5. 个人观点应该强制Facade成为子系统唯一入口点
  6. Decorator模式不允许修改接口


参考阅读:
 · 工厂模式与OO设计原则
· 视角的力量--再说OO设计原则
· OO设计原则总结