设计模式总结

第一部分 适应设计模式

Iterator模式(迭代器模式)

迭代器模式用于按序遍历容器中的元素,由于遍历的顺序可以又多种方式(以树为例,可以是深度遍历也可以是广度遍历),如果将遍历的方式写入容器类当中,会导致一下几个问题:

  • 不断向集合中添加遍历算法会模糊其 “高效存储数据” 的主要职责。
  • 客户端并不在乎数据的存储方式,人家只想要能够以某种特定的方式遍历数据即可

因此引入了迭代器模式,可以让你能在不暴露集合底层表现形式 (列表、 栈和树等)的情况下遍历集合中所有的元素。

结构

将迭代器聚合进入数据结构当中客户端之间使用这个迭代器来遍历这个数据结构

在这里插入图片描述

  • Iterator(迭代器)
    它定义了访问和遍历元素的接口,声明了用于遍历数据元素的方法hasNext和next两个方法。hasNext用于判断是否还有元素,next方法用于获取下一个元素。
  • ConcreteIterator(具体的迭代器)
    它实现了抽象迭代器接口,完成对聚合对象的遍历,同时在具体迭代器中通过游标来记录在聚合对象中所处的当前位置,在具体实现时,游标通常是一个表示位置的非负整数
  • Aggregate(集合)
    它用于存储和管理元素对象,声明一个getIterator() 方法用于创建一个迭代器对象,充当抽象迭代器工厂角色。
  • ConcreteAggregate(具体的集合)
    它实现了在抽象聚合类中声明的getIterator() 方法,该方法返回一个与该具体聚合类对应的具体迭代器ConcreteIterator 实例。

优点

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。
    • 在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。
    • 由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
  • 满足“开闭原则”的要求
    • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,

缺点

  • 在一定程度上增加了系统的复杂性。
    • 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加
  • 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展。
    • 例如JDK 内置迭代器Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator 等来实现,而ListIterator 迭代器无法用于操作Set类型的聚合对象。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是件很容易的事情。

适用场景

  • 当集合背后为复杂的数据结构,且你希望对客户端隐藏其复杂性时(出于使用便利性或安全性的考虑),可以使用迭代器模式。
    • 迭代器封装了与复杂数据结构进行交互的细节,为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便, 而且能避免客户端在直接与集合交互时执行错误或有害的操作, 从而起到保护集合的作用。
  • 想要减少程序中重复的遍历代码。
    • 重要迭代算法的代码往往体积非常庞大。 当这些代码被放置在程序业务逻辑中时, 它会让原始代码的职责模糊不清, 降低其可维护性。 因此, 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。
  • 希望代码能够遍历不同的甚至是无法预知的数据结构,可以使用迭代器模式。
    • 该模式为集合和迭代器提供了一些通用接口。 如果你在代码中使用了这些接口, 那么将其他实现了这些接口的集合和迭代器传递给它时, 它仍将可以正常运行。

相关设计模式

  • Visitor(访问者)

    在迭代器模式中是对集合一个一个进行遍历,而访问者模式则是在遍历元素的集合过程中对元素进行相同的处理

    在集合遍历过程中对元素进行固定的处理操作时可以与访问者模式相结合

  • Composite(组合)

    组合模式是具有递归结构的模式,在其适应迭代器模式比较困难

  • Factory Method(工厂方法)

    在iterator方法中生成Iterator的实例时可能会使用工厂方法模式

Adapter模式(适配器模式)

适配器模式(Adapter Pattern)可以将一个类的接口和另一个类的接口匹配起来,它通常用于使现有类与其他类一起工作,而无须修改原来的适配者接口和抽象目标类接口,目的是为了复用原来的适配者接口和抽象目标类接口。
可以使用适配器模式将类的接口转换为客户端期望的另一个接口,让那些接口不兼容的类可以一起工作。

结构

根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

  1. 类适配器

在这里插入图片描述

  1. 对象适配器

在这里插入图片描述

  • Target(目标抽象类)
    目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
  • Adapter(适配器类)
    适配器可以调用另一个接口,作为一个转换器,对Adaptee 和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee 对象使二者产生联系。
  • Adaptee (适配者类)
    适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

优点

  1. 类适配器
    • 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
      • 重写想要置换的方法即可
  2. 对象适配器
    • 一个对象适配器可以把多个不同的适配者适配到同一个目标;
    • 可以适配一个适配者的子类
      • 由于适配器和适配者之间是关联关系,根据“里氏替换原则”,适配者的子类也可通过该适配器进行适配。

缺点

  1. 类适配器

    • 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
    • 适配者类不能为最终类,如在Java中不能为final类;
    • 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
      • 因为Java是单继承,已经继承了适配者类了所有不能再继承目标类了,所有目标类只能为接口
  2. 对象适配器

    • 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦
      • 如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

适用场景

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
    • 很多时候我们并非从0开始编程,经常会使用到现有的类,故我们会更愿意将这些类作为组件重复利用,用已有类来实现现需功能,比如说版本迭代更新,我们可以使用适配器模式解决版本的兼容问题
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

相关设计模式

  • Bridge模式
    适配器模式用于连接接口不同的类,而桥接模式则用于间接类的功能层次结构与实现层次结构
  • Decorator模式
    适配器模式用于填补不同接口之间的缝隙,而装饰者模式则是再不改变接口的前提下增加功能

第二部分 交给子类

Template Method(模板方法模式)

在面向对象程序设计过程中,程序员常常会遇到这种情况:算法的执行步骤已知,而每一步的具体实现未知,我们可以采用模板方法模式,定义一个算法骨架,而将算法的一些步骤的具体实现延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

结构

就是定义一个抽象类或者接口,让子类去继承或者实现,注意模板方法要用final修饰

在这里插入图片描述

  • 抽象类(Abstract Class)
    负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
      • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
        一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。
  • 具体子类(Concrete Class)
    实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

优点

  • 提高代码复用性
    • 将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
  • 实现了反向控制
    • 通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。
    • 原本是站在子类的角度思考问题,子类可以实现父类哪些方法?子类可以重写父类哪些方法?子类应该增加哪些方法?
      现在是站在更抽象的父类的角度思考问题,期望子类实现哪些方法?要求子类去实现哪些方法?

缺点

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

相关设计模式

  • Factory Method模式
    工厂方法模式是模板方法模式用于生成实例的一个典型例子
  • Strategy模式
    可以使用委托模式替换整个算法

Factory Method模式(工厂方法模式)

工厂方法模式也被称为虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。

定义一个用于创建对象的抽象工厂,让子类决定将哪一个类实例化,也就是一个具体工厂生成一种实例。工厂方法模式让一个类的实例化延迟到其子类。

在工厂方法模式中,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构(模板方法模式)。

结构

在这里插入图片描述

  • Product(抽象产品)
    它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
  • ConcreteProduct(具体产品)
    它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
  • Factory(抽象工厂)
    在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
  • ConcreteFactory(具体工厂)
    它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

优点

  • 用户只需要关心所需产品对应的工厂,无须关心创建细节
    • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户甚至无须知道具体产品类的类名
  • 能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部
    • 工厂角色和产品角色的设计都是基于多态性的,所有的具体工厂类都具有同一抽象父类,因此工厂方法模式又被称为多态工厂模式。
  • 符合“开闭原则”
    • 在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了

缺点

  • 类爆炸和系统额外开销
    • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  • 增加了系统的实现难度
    • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到配置文件、反射等技术

适用场景

  • 客户端不需要知道它所需要的对象的类。

    • 在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
  • 抽象工厂类通过其子类来指定创建哪个对象。

    • 在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。

相关设计模式

  • Template Method模式
    工厂方法模式就是模板方法模式的典型应用
  • Singleton模式
    我们可以使用单例模式来初始化工厂,因为工厂只需要一个就够了
  • Composite模式
    产品角色可以使用组合模式
  • Iterator模式
    迭代器模式中使用iterator生产Iterator时可以使用工厂方法模式

第三部分 生成实例

Singleton模式(单例模式)

只生成一个实例

单例设计模式分类两种:

  • 饿汉式:类加载就会导致该单实例对象被创建
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
  1. 饿汉式

    这种方式最简单,也没有并发问题和效率问题,但是在类加载时就初始化,有些浪费内存,因为有可能这个方法自始至终都不会被调用到,尤其是在一些对外提供的工具包或 API 时应该尽量避免这种方式。

    class Singleton {
    
        //1. 构造器私有化
        private Singleton() {}
    
        //2. 提供一个私有的静态常量类对象
        private final static Singleton instance = new Singleton();
    
        //3. 提供一个公有的静态方法以获取对象
        public static Singleton getInstance() {
            return instance;
        }
    }
    
  2. 懒汉式

    • 双重检查锁

      这种采用双锁机制的方式,安全且在多线程情况下能保持高性能。

      volatile关键字的作用:
      要理解这个问题,先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:
        (1)分配内存空间。
        (2)初始化对象。
        (3)将内存空间的地址赋值给对应的引用。
      但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
        (1)分配内存空间。
        (2)将内存空间的地址赋值给对应的引用。
        (3)初始化对象

      如果进行了重排序那么当一个线程执行到了第二步将内存空间的地址赋值给对应的引用此时instance != null 但对象并没有进行初始化,此时另一个线程进行if判断发现instance != null就会之间返回instance那就相当于返回了一个空对象线程不安全,加上volatile关键字目的就是不要让操作系统进行重排序解决线程不安全问题

      class Singleton {
      
          //1. 提供一个私有的构造函数避免被new
          private Singleton() {}
          
          //2. 定义一个静态的类对象,并加上volatile字段
          private static volatile Singleton instance;
          
          //3. 定义一个公有的的方法如果存在类对象则之间返回如果没有则先创建,对创建对象实例是进行线程同步,实际并没有什么用
          public static Singleton getInstance() {
              //第一次判断,如果已经创建完实例了,之后就之间进不去了,就不会用到synchronized字段消耗性能
              if (instance == null) {
                  synchronized (Singleton.class) {
                      //第二次判断此时已经线程同步只有一个线程进来所以线程是安全的,下一个线程进来使已经创建实例就不会重复创建
                      if (instance == null) {
                          instance = new Singleton();
                      }
                  }
              }
              return instance;
          }
      }
      
    • 静态内部类方式

      这种方式能达到双检锁方式一样的功效,但实现更简单。它利用了 JVM 的类机制来保证初始化instance 时只有一个线程。

      public class Singleton {
      	private Singleton() {}
      	private static class HolderClass {
      	private static final Singleton INSTANCE = new Singleton();
      	}
      	public static Singleton getInstance() {
      		return HolderClass.INSTANCE;
      	}
      }
      

结构

在这里插入图片描述

单例模式结构图中只包含一个单例角色:

  • Singleton(单例)
    在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance ()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。

优点

  • 提供了对唯一实例的受控访问。
    • 因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
  • 节约系统资源
    • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
  • 允许可变数目的实例(多例模式)。
    • 基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例对象共享过多有损性能的问题。

缺点

  • 扩展困难
    • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”
    • 因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
  • 可能会被垃圾回收
    • 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。

适用场景

  • 系统只需要一个实例对象
    • 如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
  • 客户调用类的单个实例只允许使用一个公共访问点
    • 除了该公共访问点,不能通过其他途径访问该实例。

相关设计模式

以下模式中多数只会生成一个实例

  • AbstractFactory模式:抽象工厂模式
  • Bulider模式:创建者模式
  • Facade模式:外观模式
  • Prototype模式:原型模式

Prototype模式(原型模式)

通过复制生成实例
原型模式不是用来获得性能优势的。它仅用于从原型实例创建新对象。

在很多情况下我们不能通过类名来获取对象,而是直接通过拷贝一个对象获得一个新的对象,比如:

  • 对象种类繁多,无法将它们整合到一个类中时,类似于模板方法模式
  • 难以根据类生成实例时:有些类创建起来非常复杂,因为已经被用户进行自定义过了,想要获得一模一样的对象就很难new出来,使用clone回方便一些
  • 想解耦框架与生成的实例时:将clone实例封装到framework中,客户端直接传参就可获取对象,这其实也是工厂方法模式的一种体现

结构

cloneable其实就是一个标记接口,只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常

在这里插入图片描述

  • Prototype(抽象原型类)
    它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
  • ConcretePrototype (具体原型类)
    它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
  • Client(客户类)
    让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。

优点

  • 简化对象的创建过程
    • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
  • 扩展性较好
    • 由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,将具体原型实现类写在配置文件中,增加或减少产品类对原有系统都没有任何影响
  • 原型模式提供了简化的创建结构
    • 工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
  • 可以使用深克隆的方式保存对象的状态
    • 使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。

缺点

  • 违背了“开闭原则”
    • 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码
  • 深克隆比较麻烦
    • 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

适用场景

  • 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
  • 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
  • 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

相关设计模式

  • Flyweight模式
    使用Prototype模式可以生成一个与当前实例状态完全相同的实例而使用Flyweight模式可以在不同的地方使用同一个实例
  • Memento模式
    使用Prototype模式可以生成一个与当前实例状态完全相同的实例而使用Memento模式可以保存当前实例的状态,以实现快照和撤销功能
  • Composite模式以及Decorator模式
    使用Composite模式以及Decorator模式模式,要能够动态的创建复杂结构的实例。这事可以使用原型模式,以版主我们方便生成实例
  • Command模式
    想要复制命令模式中出现的命令时,可以使用原型模式

Builder模式(建造者模式)

组装复杂的实例

当通过构造器实现对象构建参数初始化,如果对象属性比较多,导致构造器的参数个数不可控。
故可以使用建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程(有点像模板方法模式)可以创建不同的表示。

建造者模式的核心在于如何一步步构建一个包含多个组成部件的完整对象,使用相同的构建过程构建不同的产品,在软件开发中,如果我们需要创建复杂对象并希望系统具备很好的灵活性和可扩展性可以考虑使用建造者模式。

结构

建造者模式一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

在这里插入图片描述

  • Builder(抽象建造者)
    它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartXXXX (),它们用于创建复杂对象的各个部件;另一类方法是build(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。
  • ConcreteBuilder (具体建造者)
    它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。
  • Product(产品角色)
    它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。
  • Director(指挥者)
    它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。原本构建一个实例需要一堆参数现在只需要一个建造者参数。客户端也能作为指挥者,这样的话,让产品聚合一个Builder。

优点

  • 客户端不必知道产品内部组成的细节,将产品本身与产品创建解耦合,使得相同的创建过程可以创建不同的对象
  • 每一个具体建造者都相对独立,因此可以很轻易的通过新增一个类的方式创建不同的产品对象,符合开闭原则
  • 可以更加精细的控制产品的创建过程

缺点

  • 建造者模式所创建的产品一般具有较多共同点,如果产品间差异较大则不适应
  • 如果产品内部变化复杂,可能导致类爆炸,需要大量的建造者类

适用场景

  • 需要生产的产品对象有复杂的内部结构(解放构造函数)
  • 需要生产的产品对象属性间相互依赖,需要按顺序进行生产对象的创建过程独立于创建该类,就是说不能通过简单的new创建,还需要进行加工(创建过程和属性设置进行了一次隔离)
  • 隔离复杂对象的创建和使用

相关设计模式

  • Template Method模式
    Director使用模板方法模式控制Builder
  • Composite模式
    有些情况下建造者模式生成的实例构成了组合模式
  • Abstrat Factory模式
    建造者模式和抽象工厂模式均用于生成复杂实例
  • Facade模式
    建造者模式,Director角色通过组合Builder角色中的复杂方法最后向客户端提供一个简单的API
    外观模式时通过组合自身内部模块向客户端提供一个简单的API

Abstract Factory模式(抽象工厂模式)

工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时,我们可以考虑将一些相关的产品组成一个“产品族”由同一个工厂来统一生产,这就是抽象工厂模式(Abstract Factory Pattern)的基本思想。

产品族:指由同一个工厂生产的,位于不同产品等级结构中的一组产品,比如华为手机,华为电脑,华为平板都是属于华为族
产品等级:产品等级结构即产品的继承结构,比如华为手机,苹果手机,小米手机都继承于手机

结构

在这里插入图片描述

  • AbstractFactory(抽象工厂)
    它声明了一组用于创建一组产品的方法,每一个方法对应一种产品。
  • ConcreteFactory(具体工厂)
    它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
  • AbstractProduct(抽象产品)
    它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
  • ConcreteProduct(具体产品)
    它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。

优点

  • 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。
    • 由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象
  • 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。

缺点

  • “开闭原则”的倾斜性
    • 在抽象工厂模式中,增加新的产品族很方便,但是增加新的产品等级结构很麻烦,抽象工厂模式的这种性质称为“开闭原则”的倾斜性。
  • 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。

适用场景

  • 用户无须关心对象的创建过程,将对象的创建和使用解耦
    • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的
  • 系统中有多于一个的产品族,而每次只使用其中某一产品族
    • 可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
  • 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来
    • 同一个产品族中的产品可以是没有任何关系的对象,但是它们都具有一些共同的约束,如同一操作系统下的按钮和文本框,按钮与文本框之间没有直接关系,但它们都是属于某一操作系统的,此时具有一个共同的约束条件:操作系统的类型。
  • 产品等级结构稳定
    • 设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。

相关设计模式

  • Builder模式
    抽象工厂模式通过调用抽象产品的接口来组装抽象产品,而建造者模式是分阶段制作复制实例的
  • FactoryMethod模式
    又是抽象工厂模式中零件和产品的生成回使用工厂方法模式
  • Composite模式
    又是抽象工厂模式在制作产品时会使用组合模式
  • Singleton模式
    抽象工厂模式中具体工厂会使用单例模式

第四部分 分开考虑

Bridge模式(桥接模式)

桥接模式本质上就是将类的功能层次结构和类的实现层次结构分离。

  • 类的功能层次结构:父类具有一些基本功能然后子类去继承父类添加一些新的功能,达到功能扩展的目的
  • 类的实现层次结构:父类声明一些抽象方法然后子类去继承实现这些抽象方法,达到任务分担的目的

如果只有实现层次结构那么想要扩展功能时,父类得要添加新的抽象方法,然后子类也需要实现这个新的抽象方法
如果只有功能层次结构那么想要拥有不同的实现方式时,就需要重新写一个新的父类供子类去继承

结构

将类的功能层次结构和类的实现层次结构分离后,将实现层次聚合到功能层次的方式来将这两个层次再次组合。

在这里插入图片描述

  • Abstraction(抽象类)
    类的功能层次的最上层它聚合并使用Implementor定义的方法来实现自生的基本方法
  • RefinedAbstraction(改善后的抽象化)
    继承抽象化扩展添加新的功能
  • Implementor(实现者)
    类的实现层次结构的最上层负责定义接口API
  • ConcreteImplementor(具体实现者)
    负责实现Implementor定义的接口

优点

  • 分离抽象接口及其实现部分
    • 桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
  • 桥接模式可以取代多层继承方案
    • 多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。
  • 桥接模式提高了系统的可扩展性
    • 在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。

缺点

  • 增加系统的理解与设计难度
    • 由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程
  • 使用范围具有一定的局限性
    • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。

适用场景

  • 想要拆分或重组一个具有多重功能的庞杂类(例如能与多个数据库服务器进行交互的类)
  • 希望在几个独立维度上扩展一个类
  • 需要在运行时切换不同实现方法

相关设计模式

  • Template Method模式
    类的实现层次结构中父类定义方法,子类实现方法,这时模板方法的一种体现
  • Abstract Factory模式
    为例能够需求设计出良好的ConcreteImplementor(具体实现者),又是会使用抽象工厂方法模式
  • Adapter模式
    桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。

Strategy模式(策略模式)

策略在编程当中可以理解为策略

策略模式就是整体地替换算法

结构

在这里插入图片描述

  • Strategy(策略)
    负责定义实现策略所必须实现的接口API
  • ConcreteStrategy(具体的策略)
    实现策略的API
  • Context(上下文)
    负责使用Strategy

优点

  • 策略类之间可以自由切换
    • 由于策略类都实现同一个接口,所以使它们之间可以自由切换。
  • 易于扩展
    • 增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
  • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

适用场景

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

相关设计模式

  • Flyweight模式
    有时会使用享元模式让多个递归可以共用ConcreStrategy角色
  • Abstract Factory模式
    可以使用策略模式整体替换算法
    可以使用抽象工厂方法模式可以整体替换具体工厂、零件和产品
  • State模式
    使用策略模式和状态模式都可以替换被委托对象,而且他们的类之间的关系也很相似,但是两种模式的目的不同
    策略模式中ConcreteStrategy角色表示算法类,在策略模式中,可以替换被委托的对象的类。当然如果没有毕业,也可以不替换
    状态模式中ConcreteState角色是表示状态的类。在状态模式中,每次状态变化时,被委托的累都必定会被替换

第五部分 一致性

Composite模式(组合模式)

容器与内容的一致性

树状结构

组合模式描述了在对待一组对象实例的时候,使用以单个对象实例相同的方式对待。
组合的目的是将对象“组合”成树形结构,以表示部分-整体层次结构
通过实现组合模式,客户端可以统一对待各个对象与组合。

结构

**组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。**同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。

在这里插入图片描述

  • Component(抽象构件)
    使叶子和复合物具有一致性的角色,即这俩的父类,它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
  • Composite(容器构件)
    它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
  • Leaf(叶子构件)
    它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
  • Client
    客户端,组合模式的使用者

优点

  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
  • 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

缺点

  • 在增加新构件时很难对容器中的构件类型进行限制。
    • 有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。

适用场景

  • 如果你需要实现树状对象结构, 可以使用组合模式。
    • 组合模式为你提供了两种共享公共接口的基本元素类型: 简单叶节点和复杂容器。 容器中可以包含叶节点和其他容器。 这使得你可以构建树状嵌套递归对象结构。
  • 如果你希望客户端代码以相同方式处理简单和复杂元素, 可以使用该模式。
    • 组合模式中定义的所有元素共用同一个接口。 在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。

相关设计模式

  • Command模式
    使用命令模式编写宏命令使使用了组合模式

  • Visitor模式
    使用访问者模式访问组合模式的递归结构

  • Decorator模式
    组合模式通过Component角色使容器和内容有了一致性
    装饰器模式使装饰框和内容具有一致性

Decorator模式(装饰器模式)

装饰边框与被装饰物的一致性

装饰模式(Decorator Pattern)可以在不改变一个对象本身功能的基础上给对象增加额外的新行为,可以用于替代继承

动态的给一个对象增加一些额外的职责。就扩展功能而言,装饰器模式提供了一种比使用子类更灵活的方式。

结构

装饰器模式通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。

在这里插入图片描述

在这里插入图片描述

  • Component(抽象构件)
    它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
  • ConcreteComponent (具体构件)
    它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
  • Decorator(抽象装饰类)
    它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
  • ConcreteDecorator (具体装饰类)
    它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

优点

  • 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
  • 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。

缺点

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能
  • 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

适用场景

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  • 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。

    不能采用继承的情况主要有两类:

    • 第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;
    • 第二类是因为类已定义为不能被继承(如Java语言中的final类)。

相关设计模式

  • Adapter模式
    装饰者模式可以在不改变装饰物的接口API前提下,为被装饰物添加边框
    适配器模式适用于适配两个不同的接口
  • Stragety模式
    装饰者模式可以改变装饰物的边框或是为被装饰物添加多重边框那样,来增加类的功能
    册略模式通过整体的替换算法来改变类的功能

第六部分 访问数据结构

Visitor模式(访问者模式)

将数据结构与处理分离

访问者模式其实就是将数据结构与处理数据的算法进行了分离,原先是将数据结构和其对应的处理算法写在同一个类当中,现在将处理算法部分封装到访问者类当中,然后数据结构进行处理操作时只需要知道使用哪种处理算法也就是调用accept方法然后传入具体的Visitor。Visitor类也会对每一种元素声明访问方式。具体访问方式由其子类实现,不同的子类由不同的方法方式,所以可以由传入visitor参数来决定访问的算法,这里类似于策略模式。

结构

在这里插入图片描述

  • Visitor(访问者)
    访问者角色负责对数据结构中的每个具体元素声明一个visit方法
  • ConcreteVisitor(具体的访问者)
    也就使实现visit方法,来实现如何处理每一个具体元素,类似于策略模式
  • Element(元素)
    声明一个accept方法表示这个数据结构里的每一个元素可以被访问者访问
  • ConcreteElement(具体元素)
    负责实现Element角色定义的方法,也就是具体的数据结构中的某种类型
  • ObjectStructure(对象结构)
    由具体元素组成的数据结构

优点

  • 扩展性好
    • 在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好
    • 通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
  • 分离无关行为
    • 通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的
      功能都比较单一。

缺点

  • 对象结构变化很困难

    • 易于增加ConcreteVisitor不易于增加ConcreteElement

    • 在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

  • 违反了依赖倒置原则

    • 访问者模式依赖了具体类,而没有依赖抽象类。

适用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

相关设计模式

  • Iterator模式
    访问者模式和迭代器模式都是在某种数据结构上进行处理,迭代器模式用于逐个遍历保存在数据结构中的元素,访问者模式用于对保存在数据结构中的元素进行某种特点的处理
  • Composite模式
    有时访问者模式访问的数据结构会采用组合模式
  • Interpreter模式
    解释器模式中,有时会使用访问者模式。例如生成了语法树之后,可能会使用访问者模式访问语法树的各个节点进行处理

Chain of Responsibility模式(职责链模式)

将多个对象组成一条职责链,然后按照他们在职责链上的顺序一个一个地找出到底应该谁来负责处理

职责链模式可以弱化请求方和处理方之间的关联关系,避免一个请求对象的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一个链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。

结构

责任链模式是一种设计模式,由一个命令对象源和一系列处理对象组成。每个处理对象都包含定义它可以处理的命令对象类型的逻辑;其余的被传递给链中的下一个处理对象。

需要注意的是职责链模式并不创建职责链,职责链的创建工作必须由系统的其他部分来完成,一般是在使用该职责链的客户端中创建职责链。

在这里插入图片描述

  • Handler(抽象处理者)
    处理者中定义了抽象请求处理方法定义了抽象抽象处理者类型的对象next,如果自己无法处理会交给下一个处理者,一般设计为抽象类。
  • ConcreteHandler (具体处理者)
    它是抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者;在具体处理者中可以访问链中下一个对象,以便请求的转发。

优点

  • 降低了系统的耦合度
    • 职责链模式使得一个对象无须知道是其他哪一个对象处理其请求,对象仅需知道该请求会被处理即可,接收者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构
  • 简化对象的相互连接
    • 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用
  • 更多的灵活性
    • 在给对象分派职责时,职责链可以给我们更多的灵活性,可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的职责。
  • 符合“开闭原则”
    • 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可

缺点

  • 由于一个请求没有明确的接收者,那么就不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。
  • 对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不太方便。
  • 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。

适用场景

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。
  • 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。

相关设计模式

  • Composite模式
    处理者角色进程会使用组合模式
  • Command模式
    有时会使用命令模式向处理者发送请求

第七部分 简单化

Facade模式(外观模式)

就是一堆类各种奇奇怪怪的调用如果让客户端来调用比较复杂,现在提供一个类来统一调用这些类最后提供一个接口给客户端

结构

在这里插入图片描述

  • 外观(Facade)角色
    为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色
    实现系统的部分功能,客户可以通过外观角色访问它。

优点

  • 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
  • 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
  • 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。

缺点

  • 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。
  • 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。

适用场景

  • 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。
  • 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

相关设计模式

  • Abstract Factory模式
    抽象工厂模式可以看作是一个生成负责实例时的外观模式,因为这个工厂的目的就是说,客户端如果相应获得实例调用这个API就好了
  • Singlenton模式
    有时会使用单例模式来创建
  • Mediator模式
    在外观模式中,外观角色单方面地使用其他角色来提供高层接口
    而中介者模式中,Mediator作为Colleague之间的仲裁者负责调停
    外观模式是单向的,仲裁者模式是双向的

Mediator模式(仲裁者模式)

只有一个仲裁者,将所有同时聚合到这个仲裁者当中,然后进行仲裁,并且双方均要给出能够与对方进行通信的API

用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

结构

中介模式定义了一个对象,该对象封装了一组对象如何交互。使用中介模式,对象之间的通信被封装在中介对象中。对象不再直接相互通信,而是通过中介器进行通信。这减少了通信对象之间的依赖性,从而减少了耦合。

在这里插入图片描述

  • 中介者 (Mediator)
    它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  • ConcreteMediator(具体的中介者)
    负责实现Mediator的抽象方法,并且聚合所有的同时类
  • Colleague(同事)
    定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • ConcreteColleague(具体同事)
    是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

优点

  • 中介者模式简化了对象之间的交互
    • 它用中介者和同事的一对多交互代替了原来同事之间的多对多交互,一对多关系更容易理解、维护和扩展,将原本难以理解的网状结构转换成相对简单的星型结构。
  • 中介者模式可将各同事对象解耦。
    • 中介者有利于各同事之间的松耦合,我们可以独立的改变和复用每一个同事和中介者,增加新的中介者和新的同事类都比较方便,更好地符合“开闭原则”。
  • 可以减少子类生成
    • 中介者将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,这使各个同事类可被重用,无须对同事类进行扩展。

缺点

  • 一段时间后, 中介者可能会演化成为上帝对象。
    • 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
  • 同事类容易复用,仲裁者难以复用

适用场景

  • 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
  • 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

相关设计模式

  • Facade模式
    在仲裁者模式中,仲裁者与同时进行交换,而在外观模式中,Facade角色单方面地使用其他角色来对外提供高层接口
    仲裁者模式是双向的,外观模式是单向的
  • Observer模式
    有时会使用观察者模式来实现Mediator角色和Colleague角色间的通信

第八部分 管理状态

Observer模式(观察者模式)

发送状态变化通知

在软件系统中对象并不是孤立存在的,一个对象行为的改变可能会导致一个或多个其他与之存在依赖关系行为发生改变,它们之间将会产生联动,“触一发而动百发”。为了更好的描述对象之间存在的这种一对多的联动,产生了观察者模式(Observer Pattern)这一解决方案。

观察者并非主动的去观察而是被动的接受Subject角色通知故,故又被称为发布-订阅(Publish/Subscribe)模式。它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己

结构

观察者模式是一种软件设计模式,其中一个称为主体的对象维护其依赖项列表,称为观察者,并通常通过调用其方法之一自动将任何状态更改通知它们。

定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时其相关依赖对象都得到通知并被自动更新。

在这里插入图片描述

  • Subject(目标)
    目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notifyObservers()。目标类可以是接口,也可以是抽象类或具体类。
  • ConcreteSubject (具体目标)
    具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法。如果无须扩展目标类,则具体目标类可以省略。
  • Observer(观察者)
    观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。
  • ConcreteObserver (具体观察者)
    在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。通常在实现时,可以调用具体目标类的addObserver()方法将自己添加到目标类的集合中或通过deleteObserver()方法将自己从目标类的集合中删除。

优点

  • 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
  • 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
  • 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

缺点

  • 如果一个观察目标对象有很多直接和间接观察者,通知所有的观察者会花费很多时间。
  • 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

适用场景

  • 当一个对象状态的改变需要改变其他对象时,可使用观察者模式。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

相关设计模式

  • Mediator模式
    在仲裁者模式中,有时会使用观察者模式来实现Mediator角色与Colleague角色之间的通信。
    仲裁者模式中仲裁者对同事发送通知目的是为了,对同事进行仲裁
    观察者模式被观察给观察者发送通知目的是为了使者两个角色同步

Memento模式(备忘录模式)

保存对象状态

一般使用备忘录模式来实现撤销、重做、历史记录、快照等功能

在不破坏封装的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先的状态。

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,当前很多软件都提供了撤销(Undo)操作,其中就使用了备忘录模式。备忘录模式又称为标记(Token)模式。

结构

在这里插入图片描述

  • Originator(生成者)
    它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
  • Memento(备忘录)
    存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考生成者的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了生成者本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。备忘录角色有两种接口:
    • wide interface——宽接口:用于获取恢复对象状态信息的方法集合,宽接口会暴露所有的备忘录角色信息,故宽接口只给生成者开放
    • narrow interface——窄接口:对外提供非常有限的信息
  • Caretaker(负责人)
    负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。

优点

  • 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
  • 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。

缺点

  • 资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。

适用场景

  • 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时它能够恢复到先前的状态,实现撤销操作。
  • 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。

相关设计模式

  • Command模式
    使用命令模式时,可以使用备忘录模式撤销功能
  • Prototype模式
    备忘录模式只恢复需要恢复的状态信息而原型模式是完全拷贝一个一模一样的
  • State模式
    状态模式模式中状态是用类来表示的,备忘录模式中是用对象来表示

State模式(状态模式)

用类来表示状态以及状态转移

在软件系统中,有些对象也像水一样具有多种状态,这些状态在某些情况下能够相互转换,而且对象在不同的状态下也将具有不同的行为。如果使用复杂的条件判断语句(如if或switch)来进行状态的判断和转换操作,这会导致代码的可维护性和灵活性下降,特别是出现新状态的时候代码的扩展性很差,客户端代码也需要进行修改,违反开闭原则。为了更好地对这些具有多种状态的对象进行设计,我们可以使用一种被称之为状态模式(State Pattern)的设计模式。

状态模式是一种行为软件设计模式,它允许对象在其内部状态发生变化时改变其行为。这种模式接近于有限状态机的概念。状态模式可以解释为策略模式,它能够通过调用模式接口中定义的方法来切换策略。

有限状态机( FSM ) 或有限状态自动机( FSA,复数:自动机)、有限自动机,或简称状态机,是计算的数学模型。它是一种抽象机器,可以在任何给定时间恰好处于有限数量状态之一

结构

在这里插入图片描述

  • Context(环境类)
    环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
  • State(抽象状态类)
    它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
  • ConcreteState(具体状态类)
    它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。

优点

  • 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
  • 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点

  • 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
  • 状态模式对“开闭原则”的支持并不太好
    • 增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;
    • 修改某个状态类的行为也需修改对应类的源代码。

适用场景

  • 如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。
  • 如果某个类需要根据成员变量的当前值改变自身行为,从而需要使用大量的条件语句时,可使用该模式。
  • 当相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。

相关设计模式

  • Singleton模式
    单例模式常常出现在具体角色中
  • FlyWeight模式
    我们可以使用享元模式共享ConcreteState角色

第九部分 避免浪费

FlyWeight模式(享元模式)

又叫蝇量模式,就是通过共享对象的方式使程序变轻

共享对象,避免浪费

通过进行共享实例来避免new对象

类比字符串常量池

注意区分内部信息和外部信息

  • Intrinsic信息(内部):不依赖位置与状况,可以共享
  • Extrinsic信息(外部):依赖位置与状况,不可以共享

结构

在这里插入图片描述

  • 享元 (Flyweight)
    类包含原始对象中部分能在多个对象中共享的状态。 同一享元对象可在许多不同情景中使用。 享元中存储的状态被称为 “内在状态”。 传递给享元方法的状态被称为 “外在状态”。
  • 享元工厂 (Flyweight Factory)
    会对已有享元的缓存池进行管理。 有了工厂后, 客户端就无需直接创建享元, 它们只需调用工厂并向其传递目标享元的一些内在状态即可。 工厂会根据参数在之前已创建的享元中进行查找, 如果找到满足条件的享元就将其返回; 如果没有找到就根据参数新建享元。
  • 情景 (Context)
    类包含原始对象中各不相同的外在状态。 情景与享元对象组合在一起就能表示原始对象的全部状态。

优点

  • 如果程序中有很多相似对象, 那么你将可以节省大量内存。
  • 享元模式的外在状态相对独立,而且不会影响其内在状态,从而使得享元对象可以在不同的环境中被共享。

缺点

  • 享元模式使得系统变得复杂,需要分离出内在状态和外在状态,这使得程序的逻辑复杂化。
  • 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。

适用场景

  • 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

相关设计模式

  • Proxy模式
    享元模式通过减少new对象的形式提供程序效率,而代理模式通过设置代理提高程序效率
  • Composite模式
    有时组合模式的Leaf角色需要使用享元模式
  • Singleton模式
    享元工厂角色会使用到单例模式

Proxy模式(代理模式)

代理的使用可以简单地转发到真实对象,或者可以提供额外的逻辑。

  • 虚拟代理:虚拟代理(Virtual Proxy)也是一种常见的代理模式,对于一些占用系统资源较多的或者加载时间长的对象,可以给系统提供一个虚拟代理。
  • 远程代理:远程代理(Remote Proxy)是一种常用的代理模式,它使得客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速响应并处理客户端的请求。
  • 动态代理:动态代理(Dynamic Proxy)可以让系统在运行时,根据具体实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实类而且可以代理不同的方法,它在事务管理、AOP 等领域中发挥了重要作用。

结构

  1. Virtual Proxy虚拟代理

在这里插入图片描述

  1. JDK动态代理

在这里插入图片描述

public Subject getProxyInstance() {
    /**
     * ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法固定
     * Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确定类型,获取方法也是固定的
     * reflect.InvocationHandler h:事情处理,执行目标对象的方法时,会触发事情处理器方法,会把当前目标对象方法作为参数传入
     */
    return (Subject)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
        /*
        InvocationHandler中invoke方法参数说明:
            proxy : 代理对象
            method : 对应于在代理对象上调用的接口方法的 Method 实例
            args : 代理对象调用接口方法时传递的实际参数
    	*/
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before...");
            Object invoke = method.invoke(target, args);
            System.out.println("after...");
            return invoke;
        }
    });
}
  1. CGLib动态代理

在这里插入图片描述

public RealSubject getProxyObject() {
	//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
	Enhancer enhancer =new Enhancer();
	//设置父类的字节码对象
	enhancer.setSuperclass(target.getClass());
	//设置回调函数
	enhancer.setCallback(this);
	//创建代理对象
	RealSubject obj = (RealSubject) enhancer.create();
	return obj;
}

/*
intercept方法参数说明:
    o : 代理对象
    method : 真实对象中的方法的Method实例
    args : 实际参数
    methodProxy :代理对象中的方法的method实例
*/
public TrainStation intercept(Object o, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
    System.out.println("CGLIB动态代理方式");
    TrainStation result = (TrainStation) methodProxy.invokeSuper(o,args);
    return result;
}
  • 抽象主题(Subject)类
    通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(RealSubject)类
    实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类
    提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

优点

  • 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 客户端可以针对抽象角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
  • 代理对象可以扩展目标对象的功能。
  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用

缺点

  • 由于在客户端和真实类之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
  • 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。

适用场景

  • 当客户端对象需要访问远程主机中的对象时可以使用远程代理。
  • 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理。
  • 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
  • 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
  • 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。

相关设计模式

  • Adapter模式
    适配器模式适配了两种不同API的对象,以使他们可以一同工作,而代理模式,代理角色和实际主体角色的API相同
  • Decorator模式
    装饰器模式和代理模式很相似,但装饰器模式更加侧重于怎加新功能而代理模式侧重于减轻实际主体的负担

第十部分 用类来表现

Command模式(命令模式)

命令也是类

原本是发起者之间和接受者耦合,现在在中间添加一个命令类作为缓冲,使其解耦合。

结构

发起者聚合了一个命令,命令里又聚合了一个接收者

在这里插入图片描述

  • Command(抽象命令类)
    抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
  • ConcreteCommand (具体命令类)
    具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(action)。
  • Invoker(调用者)
    调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
  • Receiver(接收者)
    接收者执行与请求相关的操作,它具体实现对请求的业务处理。

优点

  • 降低系统的耦合度。
    • 由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
  • 新的命令可以很容易地加入到系统中。
    • 由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
  • 可以比较容易地设计一个命令队列。
  • 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。

缺点

  • 使用命令模式可能会导致某些系统有过多的具体命令类。
    • 因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。

适用场景

  • 如果你需要通过操作来参数化对象,可使用命令模式。
    • 命令模式可将特定的方法调用转化为独立对象。这一改变也带来了许多有趣的应用: 你可以将命令作为方法的参数进行传递、 将命令保存在其他对象中, 或者在运行时切换已连接的命令等。
  • 如果你想要将操作放入队列中、操作的执行或者远程执行操作,可使用命令模式。
    • 同其他对象一样, 命令也可以实现序列化 (序列化的意思是转化为字符串), 从而能方便地写入文件或数据库中。 一段时间后, 该字符串可被恢复成为最初的命令对象。 因此, 你可以延迟或计划命令的执行。 但其功能远不止如此! 使用同样的方式, 你还可以将命令放入队列、 记录命令或者通过网络发送命令。
  • 如果你想要实现操作回滚功能,可使用命令模式。
    • 为了能够回滚操作, 你需要实现已执行操作的历史记录功能。 命令历史记录是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。

相关设计模式

  • Composite模式
    有时会使用组合模式实现宏命令
  • Memento模式
    有时会使用备忘录模式来保存Command角色的历史记录
  • Protype模式
    有时会使用原型模式来负责发生的事件

Interpreter模式(解释器模式)

解释器模式(Interpreter Pattern)用于描述如何构成一个简单的语言解释器,主要应用于使用面向对象语言开发的解释器设计,所以在实际开发中很少使用到。

和编译原理语法分析阶段相似,都是生成一颗AST

结构

在这里插入图片描述

  • AbstractExpression (抽象表达式)
    在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。
  • TerminalExpression (终结符表达式)
    终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子
  • NonterminalExpression (非终结符表达式)
    非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。
  • Context (环境类)
    环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。

优点

  • 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  • 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
  • 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
  • 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。

缺点

  • 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
  • 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

适用场景

  • 一个语言的文法较为简单。
    • 对于复杂的语法,语法的类层次结构变得很大且无法管理。执行效率不是关键问题。
  • 最有效的解释器通常不是通过抽象语法树来实现,而是首先将它们翻译成另一种形式。
    • 例如,正则表达式经常被转换为状态机。但是即使这样,翻译器也可以通过解释器模式来实现,所以该模式仍然适用。

相关设计模式

  • Composite模式
    非终结符技术时多递归结构常会使用组合模式来实现
  • Flyweight模式
    有时会使用享元模式来共享终结符
  • Visitor模式
    爱推导出语法树后,有时会使用访问者模式来访问语法树的各个节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值