从封装变化的角度看设计模式——组件协做

什么是设计模式

​ 要了解设计模式,首先得清楚什么是模式。什么是模式?模式即解决一类问题的方法论,简单得来讲,就是将解决某类问题的方法概括总结到理论高度,就造成了模式。java

​ 设计模式就是将代码设计经验概括总结到理论高度而造成的。其目的就在于:1)可重用代码,2)让代码更容易为他人理解,3)保证代码的可靠性。c++

​ 使用面向对象的语言很容易,可是作到面向对象却很难。更多人用的是面向对象的语言写出结构化的代码,想一想本身编写的代码有多少是不用修改源码能够真正实现重用,或者能够实现拿来主义。这是一件很正常的事,我在学习过程中,老师们老是在说c到c++的面向对象是一种巨大的进步,面向对象也是极为难以理解的存在;而在开始的学习过程当中,我发现c++和c好像差异也不大,不就是多了一个类和对象吗?但随着愈发深刻的学习使我发现,事实并非那么简单,老师们举例时老是喜欢用到简单的对象群体,好比:人,再到男人、女人,再到拥有具体家庭身份的父亲、母亲、孩子。用这些来讲明类、对象、继承......彷佛都显得面向对象是一件垂手可得的事。程序员

​ 但事实真是如此吗?封装、粒度、依赖关系、灵活性、性能、演化、复用等等,当这些在一个系统当中交错相连,互相耦合,甚至有些东西还互相冲突时,你会发现本身可能连将系统对象化都是那么的困难。面试

​ 而在解决这些问题的过程中,也就慢慢造成了一套被反复使用、为多数人知晓、再由人分类编目的代码设计经验总结——设计模式。算法

设计原则

​ 模式既然做为一套解决方案,天然不多是没有规律而言的,而其所遵循的内在规律就是设计原则。在学习设计模式的过程中,不能脱离原则去看设计模式,而是应该透过设计模式去理解设计原则,只有深深地把握了设计原则,才能写出真正的面向对象代码,甚至创造本身的模式。编程

  1. 开闭原则(Open Close Principle)设计模式

    ​ 开闭原则的意思是:对扩展开放,对修改关闭。在程序须要进行拓展的时候,不要去修改原有的代码。这样是为了使程序的扩展性更好,更加易于维护和升级。而想要达到这样的效果,就须要使用接口和抽象类。数据结构

  1. 里氏替换原则(Liskov Substitution Principle)架构

    ​ 里氏替换原则中说,任何基类能够出现的地方,子类必定能够出现。也就是说只有当派生类能够替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也可以在基类的基础上增长新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,因此里氏代换原则是对实现抽象化的具体步骤的规范。app

  2. 依赖倒置原则(Dependence Inversion Principle)

    ​ 依赖倒置原则是开闭原则的基础,具体内容:抽象不该该依赖具体,而是具体应当依赖抽象;高层模块不该该依赖底层模块,而是高层和底层模块都要依赖抽象。由于抽象才是稳定的,这个原则想要说明的就是针对接口编程。

  3. 接口分离原则(Interface Segregation Principle)

    ​ 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另一个意思是:下降类之间的耦合度。这个原则所要求的就是尽可能将接口最小化,避免一个接口当中拥有太多不相关的功能。

  4. 迪米特法则,又称最少知道原则(Demeter Principle)

    ​ 最少知道原则是指:若是两个软件实体无须直接通讯,那么就不该当发生直接的相互调用,能够经过第三方转发该调用。其目的是下降类之间的耦合度,提升模块的相对独立性。迪米特法则在解决访问耦合方面有着很大的做用,可是其自己的应用也有着一个很大的缺点,就是对象之间的通讯形成性能的损失,这是在使用过程当中,须要去折衷考虑的。

  5. 组合复用原则(Composite Reuse Principle)

    ​ 组合复用原则或者说组合优先原则,也就是在进行功能复用的过程中,组合每每是比继承更好的选择。这是由于继承的形式会使得父类的实现细节对子类可见,从而违背了封装的目的。

  6. 单一职责原则(Single Responsibility Principle)

    ​ 一个类只容许有一个职责,即只有一个致使该类变动的缘由。类职责的变化每每就是致使类变化的缘由:也就是说若是一个类具备多种职责,就会有多种致使这个类变化的缘由,从而致使这个类的维护变得困难。

​ 设计模式是设计原则在应用体现,设计原则是解决面向对象问题处理方法。在面对访问耦合的状况下,有针对接口编程、接口分离、迪米特法则;处理继承耦合问题,有里氏替换原则、优先组合原则;在保证类的内聚时,能够采用单一职责原则、集中类的信息与行为。这一系列的原则都是为了一个目的——尽量的实现开闭。设计模式不是万能的,它是设计原则互相取舍的成果,而学习设计模式是如何抓住变化和稳定的界线才是设计模式的真谛。

GOF-23 模式分类

​ 从目的来看,即模式是用来完成什么工做的;能够划分为建立型、结构型和行为型。建立型模式与对象的建立有关,结构型模式处理类或对象的组合,行为型模式对类和对象怎样分配职责进行描述。

​ 从范围来看,即模式是做用于类仍是对象;能够划分为类模式和对象模式。类模式处理类和子类之间的关系,这些关系经过继承创建,是静态的,在编译时刻就肯定下来了;对象模式处理对象间的关系,这些关系能够在运行时刻变化,更加具备动态性。

组合之下,就产生了如下六种模式类别:

  1. 类建立型模式:将对象的建立工做延迟到子类中。

  2. 对象建立型模式:将对象的建立延工做迟到另外一个对象的中。

  3. 类结构型模式:使用继承机制来组合类。

  4. 对象建立型模式:描述对象的组装形式。

  5. 类行为型模式:使用继承描述算法和控制流。

  6. 对象行为型模式:描述了一组对象怎样协做完成单个对象所没法完成的任务。

从封装变化的角度来看

​ GOF(“四人组”)对设计模式的分类更多的是从用途方法进行划分,而如今,咱们但愿从设计模式中变化和稳定结构分隔上来理解全部的设计模式,或许有着不一样的收获。

​ 首先要明白的是,得到最大限度复用的关键在于对新需求和已有需求发生变化的预见性,这也就要求系统设计可以相应地改进。而设计模式能够确保系统以特定的方式变化,从而避免系统的从新设计,而且设计模式一样容许系统结构的某个方面的变化独立于其余方面,这样就在必定程度上增强了系统的健壮性。

​ 根据封装变化,能够将设计模式划分为:组件协做、单一职责、对象建立、对象性能、接口隔离、状态变化、数据结构、行为变化以及领域问题等等。

设计模式之组件协做

​ 现代软件专业分工以后的第一个结果就是“框架与应用程序的划分”,“组件协做”就是经过晚期绑定,来实现框架与应用程序之间的松耦合,是两者之间协做时经常使用的模式。其典型模式就是模板方法、策略模式和观察者。

模板方法——类行为型模式
  1. 意图

​ 定义一个操做中的算法的骨架,并将其中一些步骤的实现延迟到子类中。模板方法使得子类能够重定义一个算法的步骤而不会改变算法的结构。

  1. 实例

​ 程序开发库和应用程序之间的调用。假设如今存在一个开发库,其内容是实现对一个文件或信息的操做,操做包含:open、read、operation、commit、close。可是呢!只有open、commit、close是肯定的,其中read须要根据具体的operation来肯定读取方式,因此这两个方法是须要开发人员本身去实现的。

​ 那咱们第一次的实现可能就是这种方式:

//标准库实现
 public class StdLibrary {
     public void open(String s){
         System.out.println("open: "+s);
     }
     public void commit(){
         System.out.println("commit operation!");
     }
     public void close(String s){
         System.out.println("close: "+s);
     }
 }复制代码
//应用程序的实现
 public class MyApplication {
     public void read(String s,String type){
         System.out.println("使用"+type+"方式read: "+s);
     }
     public void operation(){
         System.out.println("operation");
     }
 }
 //或者这样实现
 public class MyApplication extends StdLibrary{
     public void read(String s,String type){
         System.out.println("使用"+type+"方式read: "+s);
     }
     public void operation(){
         System.out.println("operation");
     }
 }复制代码
//这里两种实现方式的代码调用写在一块儿,就不分开了。
 public class MyClient {
     public static void main(String[] args){
         //方式1
         String file = "ss.txt";
         StdLibrary lib = new StdLibrary();
         MyApplication app = new MyApplication();
         lib.open(file);
         app.read(file,"STD");
         app.operation();
         lib.commit();
         lib.close(file);

         //方式2 
         MyApplication app = new MyApplication();
         app.open(file);
         app.read(file,"STD");
         app.operation();
         app.commit();
         app.close(file);
     }
 }复制代码

​ 这种实现,不管是方式1仍是方式2,对于仅仅是做为应用来讲,固然是能够的。其问题主要在什么地方呢?就方式1 而言,他是必需要使用者了解开发库和应用程序两个类,才可以正确的去应用。

​ 方式2相较于方式1,使用更加的简单些,可是仍然有不完善的地方,就是调用者,须要知道各个方法的执行顺序,这也是1和2共同存在的问题。而这恰好就是Template Method发挥的时候了,一系列操做有着明确的顺序,而且有着部分的操做不变,剩下的操做待定。

//按照Template Method结构能够将标准库做出以下修改
 public abstract class StdLibrary {
     public void open(String s){
         System.out.println("open: "+s);
     }
     public abstract void read(String s, String type);
     public abstract void operation();
     public void commit(){
         System.out.println("commit operation!");
     }
     public void close(String s){
         System.out.println("close: "+s);
     }
     public void doOperation(String s,String type){
         open(s);
         read(s,"STD");
         operation();
         commit();
         close(s);
     }
 }复制代码

​ 在修改过程当中,将原来的类修改为了抽象类,而且新增了两个抽象方法和一个doOperation()。经过使用抽象操做定义一个算法中的一些步骤,模板方法肯定了它们的前后顺序,但它容许Library和Application子类改变这些具体的步骤以知足它们各自的需求,而且还对外隐藏了算法的实现。固然,若是标准库中的不变方法不能被重定义,那么就应该将其设置为private或者final

//修改事后的Appliaction和Client
 public class MyApplication extends StdLibrary {
     @Override
     public void read(String s, String type){
         System.out.println("使用"+type+"方式read: "+s);
     }
     @Override
     public void operation(){
         System.out.println("operation");
     }
 }
 public class MyClient {
     public static void main(String[] args){
         String file = "ss.txt";
         MyApplication app = new MyApplication();
         app.doOperation(file,"STD");
     }
 }复制代码

​ 模板方法的使用在类库当中极为常见,尤为是在c++的类库当中,它是一种基本的代码复用技术。这种实现方式,产生了一种反向的控制结构,或者咱们称之为“好莱坞法则”,即“别找咱们,咱们找你”;换名话说,这种反向控制结构就是父类调用了子类的操做(父类中的doOperation()调用了子类实现的read()operation(),由于在平时,咱们的继承代码复用更多的是调用子类调用父类的操做。

  1. 结构

    templateMethod.png

  2. 参与者

    • AbstractClass(StdLibrary)

      定义抽象的原语操做(可变部分)。

      实现一个模板方法(templateMethod()),定义算法的骨架。

    • ConcreteClass(具体的实现类,如MyApplication)

      实现原语操做以完成算法中与特定子类相关的步骤。

    除了以上参与者以外,还能够有OperatedObject这样一个参与者即被操做对象。好比对文档的操做,文档又有不一样的类型,如pdf、word、txt等等;这种状况下,就须要根据不一样的文档类型,定制不一样的操做,即一个ConcreteClass对应一个OperatedObject,至关于对结构当中由一个特定操做对象,扩展到多个操做对象,而且每一个操做对象对应一个模板方法子类。

  3. 适用性

    对于模板方法的特性,其能够应用于下列状况:

    • 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
    • 各子类中公共的行为应被提取出来并集中到一个公共父类中,以免代码重复。重构方式即为首先识别现有代码中的不一样之处,而且将不一样之处分离为新的操做。最后用一个模板方法调用这些新的操做,来替换这些不一样的代码。
    • 控制子类的扩展。模板方法只有特定点调用"hook"操做,这样就只容许在这些扩展点进行相应的扩展。
  4. 相关模式

    ​ Factory Method常常被Template Method所调用。好比在参与者当中提到的,若是须要操做不一样的文件对象,那么在操做的过程当中就须要read()方法返回不一样的文件对象,而这个read()方法不正是一个Factory Method。

    ​ Strategy:Template Method使用继承来改变算法的一部分,而Strategy使用委托来改变整个算法。

  5. 思考

    • 访问控制 在定义模板的时候,除了简单的定义原语操做和算法骨架以外,操做的控制权也是须要考虑的。原语操做是能够被重定义的,因此不能设置为final,还有原语操做可否为其余不相关的类所调用,若是不能则能够设置为protected或者default。模板方法通常是不让子类重定义的,所以就须要设置为final.
    • 原语操做数量 定义模板方法的一个重要目的就是尽可能减小一个子类具体实现该算法时,必须重定义的那些原语操做的数目。由于,须要重定义的操做越多,应用程序就越冗长。
    • 命名约定 对于须要重定义的操做能够加上一个特定的前缀以便开发人员识别它们。
    • hook操做 hook操做就是指那些在模板方法中定义的能够重定义的操做,子类在必要的时候能够进行扩展。固然,若是可使用父类的操做,不扩展也是能够的;所以,在Template Method中,应该去指明哪些操做是不能被重定义的、哪些是hook(能够被重定义)以及哪些是抽象操做(必须被重定义)。
策略模式——对象行为型模式
  1. 意图

    ​ 定义一系列的算法,把它们一个个封装起来,而且使它们可相互替换。Strategy使得算法能够独立于使用它的客户而变化。

  2. 实例

    ​ 策略模式是一种很是经典的设计模式,可能也是你们常常所见到和使用的设计模式;重构过程当中选择使用策略模式的一个很是明显的特征,就是代码当中出现了多重条件分支语句,这种时候为了代码的扩展性,就能够选择使用策略模式。

    ​ 好比正面这样的代码,实现一个加减乘除运算的操做。

    public class Operation {
        public static void main(String[] args) {
            binomialOperation(1,1,'+');
            binomialOperation(1,3,'-');
            binomialOperation(1,2,'*');
            binomialOperation(1,1,'/');
            binomialOperation(1,0,'/');
        }
        public static int binomialOperation(int num1,int num2,char ch){
            switch(ch){
                case '+':
                    return num1+num2;
                case '-':
                    return num1+num2;
                case '*':
                    return num1*num2;
                case '/':
                    if(num2!=0){return num1/num2;}
                    else {
                        System.out.println("除数不能为0!");
                    }
            }
            return num2;
        }
    }复制代码

    ​ 上面的代码彻底能够实现咱们想要的功能,可是若是如今需求有变,须要再增长一个‘与’和‘或’的二目运算;那在这种状况下,势必须要去修改源码,这样就违背了开闭原则的思想。所以,使用策略模式,将上面代码修改成下列代码。

    //Strategy
    public interface BinomialOperation {
        public int operation(int num1,int num2);
    }
    public class AddOperation implements BinomialOperation {
        @Override
        public int operation(int num1, int num2) {
            return num1+num2;
        }
    }
    public class SubstractOperation  implements BinomialOperation {
        @Override
        public int operation(int num1, int num2) {
            return num1-num2;
        }
    }
    public class MultiplyOperation implements BinomialOperation {
        @Override
        public int operation(int num1, int num2) {
            return num1*num2;
        }
    }
    public class DivideOperation implements BinomialOperation {
        @Override
        public int operation(int num1, int num2) {
            if(0!=num2){
                return num1/num2;
            }else{
                System.out.println("除数不能为0!");
                return num2;
            }
        }
    }
    //Context
    public class OperatioContext {
        BinomialOperation binomialOperation;
        public void setBinomialOperation(BinomialOperation binomialOperation) {
            this.binomialOperation = binomialOperation;
        }
        public int useOperation(int num1,int num2){
            return binomialOperation.operation(num1,num2);
        }
    }
    public class Client {
        public static void main(String[] args) {
            OperatioContext oc = new OperatioContext();
            oc.setBinomialOperation(new AddOperation());
            oc.useOperation(1,2);
            //......
        }
    }复制代码
    代码很简单,就是将运算类抽象出来,造成一种策略,每一个不一样的运算符对应一个具体的策略,而且实现本身的操做。Strategy和Context相互做用以实现选定的算法。当算法被调用时,Context能够将自身做为一个参数传递给Strategy或者将所须要的数据都传递给Strategy,也就是说 `OperationContext`中`useOperation()`的`num1`和`num2`能够做为为`OperationContext`类的属性,在使用过程当中直接将`OperationContext`的对象做为一个参数传递给`Strategy`类便可。
    
       经过策略模式的实现,使得增长新的策略变得简单,可是其缺点就在于客户必须了解 不一样的策略。复制代码
  3. *结构 * Strategy.png

  4. 参与者

    • Strategy (如BinomialOperation)

      定义全部支持的算法的公共接口。Context使用这个接口来调用某具体的Strategy中定义的算法。

    • ConcreteStrategy(如AddOperation...)

      根据Strategy接口实现具体算法。

    • Context(如OperationContext)

      • 须要一个或多个ConcreteStrategy来进行配置,使用多个策略时,这些具体的策略多是不一样的策略接口的实现。好比,实现一个工资计算系统,工人身份有小时工、周结工、月结工,这种状况下,就能够将工人身份独立为一个策略,再将工资支付计划(用以判断当天是否为该工人支付工资日期)独立为一个策略,这样Context中就须要两个策略来配置。
      • 须要存放或者传递Strategy须要使用到的全部数据。
  5. 适用性

    当存在如下状况时,可使用策略模式:

    • 许多相关的类仅仅是行为有异。“策略”提供了一种多个行为中的一些行为来配置一个类的方法。
    • 须要使用一个算法的不一样变体。例如,你能够会定义一些反映不一样空间/时间权衡的算法,当这些变体须要实现为一个算法的类层次时,就能够采用策略模式。
    • 算法使用客户不该该知道的数据。能够采用策略模式避免暴露复杂的、与算法相关的数据结构。
    • 一个类定义了多种行为,而且这些行为在这个类的操做中以多个条件语句的形式出现。
  6. 相关模式

    ​ Flyweight(享元模式)的共享机制能够减小须要生成过多Strategy对象,由于在使用过程当中,策略每每是能够共享使用的。

  1. 思考

    • Strategy和Context之间的通讯问题。在Strategy和Contex接口中,必须使得ConcreteStrategy可以有效的访问它所须要的Context中的任何数据,反之亦然。这种实现通常有两种方式:

      ​ 1)让Context将数据放在参数中传递给Strategy——也就是说,将数据直接发送给Strategy。这可使得Strategy和Context之间解耦(印记耦合是能够接受的),但有可能会有一些Strategy不须要的数据。

      ​ 2)将Context自身做为一个参数传递给Strategy,该Strategy显示的向Context请求数据,或者说明在Strategy中保留一个Context的引用,这样便不须要再传递其余的数据了。

    • 让Strategy成为可选的。换名话说,在有些实现过程当中,客户能够在不指定具体策略的状况下使用Context完成本身的工做。这是由于,咱们能够为Context指定一个默认的Strategy的存在,若是有指定Strategy就使用客户指定的,若是没有,就使用默认的。

观察者模式——对象行为型模式
  1. 意图

    ​ 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都获得通知并被自动更新。

  2. 实例

    ​ 观察者模式很常见于图形用户界面当中,好比常见的Listener。观察者模式可使得应用数据的类和负责界面表示的类能够各自独立的复用。好比,当界面当中存在一个输入表单,在咱们对表单进行输入的时候,界面上又会显示这样一个数据的柱状图,以些来对比各项数据。其伪码能够描述成下列这种形式:Histogram做为柱状图类只须要负责接收数据而且显示出来,InputForm做为一个输入表单。在这个 过程当中,只要InputForm中的数据发生变化,就相应的改变Histogram 的显示。

    ​ 这种实现方式,明显在InputForm中产生了一种强耦合,若是显现图形发生变化,如今不须要显示为一个柱状图而是一个饼状图,势必又要去修改源码。

    public class Histogram {
        public void draw(int[]nums){
            for (int i:nums ) {
                System.out.print(i+"  ");
            }
        }
    }
    public class InputForm {
        private int[] data;
        Histogram histogram;
        public InputForm(Histogram histogram){
            this.histogram = histogram;
            show();
        }
        public void change(int... data){
            this.data = data;
            show();
        }
        public void show(){
            histogram.draw(data);
        }
    }
    public class Client {
        public static void main(String[] args) {
            InputForm inputForm = new InputForm(new Histogram());
            inputForm.change(3,4,5);
            inputForm.change(5,12,13);
        }
    }复制代码

    ​ 同时,InputForm和显示图形之间的关系,恰好符合观察者模式所说的一个对象的状态变化,引发其余对象的更新,同时兼顾考虑开闭问题,能够将HistogramPieChart公共特性提取出来,造成一个Graph接口。另外,有可能InputFrom不仅须要显示一种图表,而是须要同时将柱状图和饼状图显示出来,所以在InputFrom中定义的是一个List的结构来存放全部的相关显示图形。

    //Observer
    public interface Graph {
        public void update(Input input);
        public void draw();
    }
    public class Histogram implements Graph {
        private InputForm inputForm;
        public Histogram(InputForm inputForm){
            this.inputForm = inputForm;
        }
        @Override
        public void update(Input inputForm) {
            if(this.inputForm == inputForm){
                draw();
            }
        }
         @Override
        public void draw(){
            System.out.println("柱状图:");
            for (int i: inputForm.getData()) {
                System.out.println(i+"  ");
            }
            System.out.println();
        }
    }
    public class PieChart implements Graph {
        private InputForm inputForm;
        public PieChart(InputForm inputForm){
            this.inputForm = inputForm;
            this.inputForm.addGraph(this);
            draw();
        }
        @Override
        public void update(Input inputForm) {
            if(this.inputForm == inputForm){
                draw();
            }
        }
        @Override
         @Override
        public void draw(){
            System.out.println("饼状图:");
            for (int i: inputForm.getData()) {
                System.out.println(i+"  ");
            }
            System.out.println();
        }
    }
    复制代码

    ​ 在实际的应用过程当中,既然有输入表单的形式,也有可能以其余的形式输入数据,为了之后的扩展,能够将输入形式抽象出来,造成一个Input接口,以便后续的扩展。

    //Subject 目标对象
    public interface Input {
        public void addGraph(Graph graph);
        public void removeGraph(Graph graph);
        public void notifyGraphs();
    }
    public class InputForm implements Input {
        private int[] data;
        private List<Graph graphs = new List;
    
        public void change(int...data){
            this.data = data;
            notifyGraphs();
        }
        public int[] getData() {
            return data;
        }
        @Override
        public void addGraph(Graph graph){
            graphs.add(graph);
        }
        @Override
        public void removeGraph(Graph graph){
            graphs.remove(graph);
        }
        @Override
        public void notifyGraphs(){
            for (Graph g:graphs ) {
                g.update(this);
            }
        }
    }复制代码
    public class Client {
        public static void main(String[] args) {
            InputForm inputForm = new InputForm();
            Histogram h = new Histogram(inputForm);
            PieChart p = new PieChart(inputForm);
            inputForm.change(1,5,6,9,8);
            inputForm.change(2,4,6,8);
        }
    }复制代码
  3. 结构

    Observer.png

  4. 参与者

    • Subject(目标,如Input)

      • 目标须要知道本身全部的观察者对象。
      • 提供注册和删除观察者对象的接口
    • Observer(观察者,如Graph)

      为那些在目标发生变化时须要获取通知的对象定义一个更新接口。

    • ConcreteSubject(具体目标,如InputForm)

      • 将有关状态(或数据)存放到各ConcerteObserver对象中。
      • 当它的状态发生改变时,向它的各个观察者发出通知。
    • ConcreteObserver(具体观察者,如Histogram)

      • 维护一个指向ConcerteSubject的引用,或者是有关状态的引用。
      • 实现Observer的更新接口以使自身保存的目标状态与目标状态保持一致。
  5. 适用性

    在如下任一状况下可使用观察者模式:

    • 当一个抽象模型有两个方面,其中一个方面依赖于另外一个方面。将这两者封装在独立的对象中以使它们能够各自独立的改变和复用。
    • 当对一个对象的改变须要同时改变其它对象,而不知道具体有多少对象有待改变。
    • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不但愿这些对象上紧耦合的。
  6. 相关模式

    ​ Mediator(中介者模式):经过封装复杂的更新语义,可使用一个ChangeManager来充当目标和观察者之间的中介。在目标的状态变化过程当中,有些状态变化可能只是中间临时变化,而还未到最终结果,但这可能引发观察者的更新,这种频繁的更新形成的就是通讯代价和性能损失。所以,采用一个ChangeManager能够更好去管理更新操做。

    ​ Singleton(单例模式):ChangeManager可使用Singleton模式来保证它是惟一的而且是可全局访问的。

  7. 思考

    • 目标与观察者之间的映射。一个目标对象跟踪它应通知的观察者的最简单方法是显式地在目标当中保存对它们的引用,但当目标过多而观察者少时,这样存储的结构可能代价太高。其一个解决办法就是用时间换空间,用一个关联查找机制(例如一个hash表的形式)来维护目标到观察者的映射。这样没有观察者的目标天然不会产生存储上的开销,可是因为关联机制的存在,就至关于在访问观察者的过程当中多了一个步骤,就增长了访问观察者的开销。

    • 一个目标能够有不少观察者,一个观察者也一样能够观察不少目标。这种状况下,就须要多观察者的update接口做出必定的改变,使得观察者可以知道是那个目标对象发来通知。

    • 谁来触发更新。一是在对目标状态值进行设定时,自动去调用通知信息。这样客户就不须要去调用Notify(),缺点就在于多个连续的操做就会产生连续的更新,形成效率低下。二是客户本身选择合适的状况下去调用Notify(),这种触发方式优势在于客户能够在操做完成目标对象以后,一次性更新,避免了中间无用的更新。缺点在于一旦客户可能没有调用通知,就容易出错。

    • 如何保证发出通知前目标的状态自身是一致的。确保发出通知前目标状态一致这很重要,由于观察者在更新状态时,须要查询目标的当前状态。这就须要在代码序列中,保证通知是在目标状态修改完成以后进行的,这时就能够采用Template Method来固定操做的顺序。

小结

​ 在这篇文章当中,没有按照GOF对设计模式的分类来对设计模式进行描述,而是在实例的基础上,运用重构的技巧:从静态到动态、从早绑定到晚绑定、从继承到组合、从编译时依赖到运行时依赖、从紧耦合到松耦合。经过这样一种方式来理解设计模式,寻找设计模式中的稳定与变化。

​ 在上面提到的三种模式中,它们对象间的绑定关系,都是动态的,能够变化的,经过这样的方式来实现协做对象之间的松耦合,这也是“组件协做”一个特色。

​ 还有就是关于耦合的理解,有的时候耦合是不可避免的,耦合的接受程度是相对而言的,这取决于咱们在实现过程中对变化的封装和稳定的抽象折衷,这也是咱们学习设计模式的目的,就是如何利用设计模式来实现这样一种取舍。

​ 对设计模式细节描述过程,体现的是我在学习设计模式过程当中的一种思路。学习一个设计模式,首先要了解它是要干什么的。而后从一个例子出发,去理解它,思考它的一个实现过程。再而后,概括它的结构,这个结构不只仅是类图,还包括类图中的各个协做者是须要完成什么的功能、提供什么样的接口、要保存哪些数据以及各各协做者之间是如何协做的,或者说是依赖关系是怎样的。最后,再考虑与其余模式的搭配,思考模式的实现细节。

这里呢,暂时只写出了三种模式,后续的过程当中,将会一一地介绍其余的模式。


最后,最近不少小伙伴找我要Linux学习路线图,因而我根据本身的经验,利用业余时间熬夜肝了一个月,整理了一份电子书。不管你是面试仍是自我提高,相信都会对你有帮助!目录以下:

免费送给你们,只求你们金指给我点个赞!

电子书 | Linux开发学习路线图

也但愿有小伙伴能加入我,把这份电子书作得更完美!

有收获?但愿老铁们来个三连击,给更多的人看到这篇文章

推荐阅读:

相关文章
相关标签/搜索