设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

0 分类

1 创建型

创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。

1.1 单例模式

Singleton Pattern

  • 定义:确保一个类只有一个实例,并提供该实例的全局访问点。实现方式:
    1. 懒汉式:默认不会实例化,用的时候实例化。线程不安全,加同步机制来解决,或双重校验机制。
      public class Lazy {
       private Lazy() {}
       private static Lazy lazy = null; //默认不会实例化
       public static synchronized Lazy getInstance() {
           if(lazy == null)
               lazy = new Lazy();
           return lazy;
       }
      }
      
    2. 饿汉式:类加载的时候就实例化,并且创建单例对象。线程安全。
      public class Hungry {
       private Hungry() {}
       private static final Hungry hungry = new Hungry();
       public static Hungry getInstance(){
           return hungry;
       }
      }
      
  • 效果
    • 对唯一实例的受控访问。
    • 缩小名空间。
    • 允许对操作和表示的精化。
    • 允许可变数目的实例。
    • 比类操作更灵活。
  • 场景
    • 当类只能由一个实例且客户可以从一个众所周知的访问点访问它时。
    • 当这个唯一实例应该是通过子类化可以扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

1.2 简单工厂模式

Simple Factory Pattern

  • 定义:又称为静态工厂方法(Static Factory Method)模式。在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
  • 角色:简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化,被创建的实例通常都具有共同的父类。
  • 优点:实现对象的创建和对象的使用分离。
  • 缺点:不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码;产品较多时,工厂方法代码将会非常复杂。
  • 场景:
    • 工厂类负责创建的对象比较少;
    • 客户端只知道传入工厂类的参数,对于如何创建对象不关心。

1.3 工厂方法模式

Factory Pattern

  • 定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法把实例化操作推迟到子类。
  • 效果
    • 不再将与特定应用相关的类绑定到你的代码中。
    • 在一个类的内部创建对象通常比直接创建对象更灵活。
    • 可能仅仅未来创建一个特定的产品对象就不得不创建工厂子类。
  • 场景
    • 当一个类不知道它所必须创建的对象的类的时候。
    • 当一个类希望由它的子类来指定它所创建的对象的时候。
    • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

1.4 抽象工厂模式

Abstract Factory Pattern

  • 定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  • 效果
    • 它分离了具体的类。
    • 它使得易于交换产品系列。
    • 它有利于产品的一致性。
    • 难以支持新种类的产品。
  • 场景
    • 一个系统要独立于它的产品的创建、组合和表示时。
    • 一个系统要由多个产品系列中的一个来配置时。
    • 当要强调一系列相关的产品对象的设计以便进行联合使用时。
    • 当提供一个产品类库,而只想显示它们的接口而不是实现时。

1.5 原型模式

Prototype Pattern

  • 定义:使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。
  • 特点:这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。
  • 优点:性能提高;逃避构造函数的约束。
  • 缺点:配备克隆方法需要对类的功能进行通盘考虑;必须实现 Cloneable 接口。
  • 场景:
    • 类初始化需要消化非常多的资源;
    • new 产生一个对象需要非常繁琐的数据准备或访问权限;
    • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值;
    • 原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。

1.6 建造者模式

Builder Pattern

  • 定义:封装一个对象的构造过程,并允许按步骤构造。
  • 角色:一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
  • 优点:客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,符合“开闭原则”;可以更加精细地控制产品的创建过程
  • 缺点:由于建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,因此其使用范围受到一定的限制,如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
  • 场景:
    • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性;
    • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序;
    • 对象的创建过程独立于创建该对象的类;
    • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同类型的产品。

2 行为型

行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象 之间的交互。

2.1 迭代器模式

Iterator Pattern

  • 定义:提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。
  • 角色:Aggregate 是聚合类,可以产生一个 Iterator;Iterator 主要定义了 hasNext() 和 next() 方法。
  • 优点:支持以不同的方式遍历一个聚合对象;在同一个聚合上可以有多个遍历;迭代器简化了聚合类;在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  • 缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
  • 场景:
    • 访问一个聚合对象的内容而无须暴露它的内部表示;
    • 需要为聚合对象提供多种遍历方式;
    • 为遍历不同的聚合结构提供一个统一的接口。

2.2 观察者模式

Observer Pattern

  • 定义:定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。当对象间存在一对多关系时,则使用观察者模式。
  • 角色:主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。
  • 优点:可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通。
  • 缺点:如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 场景:
    • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面;
    • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变;
    • 一个对象必须通知其他对象,而并不知道这些对象是谁;
    • 需要在系统中创建一个触发链。

2.3 命令模式

Command Pattern

  • 定义:请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
  • 角色:Command-命令、Receiver-命令接收者,命令真正的执行者、Invoker-通过它来调用命令,使用命令对象的入口。
  • 优点:降低系统的耦合度,增加新的命令很方便;可以比较容易地设计一个命令队列和宏命令,并方便地实现对请求的撤销和恢复。
  • 缺点:可能会导致某些系统有过多的具体命令类。
  • 场景:
    • 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;
    • 需要在不同的时间指定请求、将请求排队和执行请求;
    • 需要支持命令的撤销操作和恢复操作,需要将一组操作组合在一起,即支持宏命令。

2.4 策略模式

Strategy Pattern

  • 定义:定义一系列算法,封装每个算法,并使它们可以互换。策略模式可以让算法独立于使用它的客户端。
  • 角色:Strategy 接口定义了一个算法族,Context 是使用到该算法族的类。
  • 优点:对“开闭原则”的完美支持,在不修改原有系统的基础上可以更换算法或者增加新的算法,它很好地管理算法族,提高了代码的复用性,是一种替换继承,避免多重条件转移语句的实现方式。
  • 缺点:客户端必须知道所有的策略类,并理解其区别,同时在一定程度上增加了系统中类的个数,可能会存在很多策略类。
  • 场景:
    • 在一个系统里面有许多类,它们之间的区别仅在于它们的行为,使用策略模式可以动态地让一个对象在许多行为中选择一种行为;
    • 一个系统需要动态地在几种算法中选择一种;
    • 避免使用难以维护的多重条件选择语句;
    • 希望在具体策略类中封装算法和与相关的数据结构。

2.5 职责链模式

Chain of Responsibility Pattern

  • 定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。
  • 角色:Handler 里面聚合它自己。
  • 优点:降低耦合度;简化了对象;增强给对象指派职责的灵活性;增加新的请求处理类很方便。
  • 缺点:不能保证请求一定被接收;系统性能将受到一定影响。
  • 场景:
    • 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定;
    • 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求;
    • 可动态指定一组对象处理请求。

2.6 状态模式

State Pattern

  • 定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
  • 角色:Context环境类又称为上下文类,它是拥有状态的对象,在环境类中维护一个抽象状态类State的实例;State抽象状态类用于定义一个接口以封装与环境类的一个特定状态相关的行为,ConcreteState具体状态类是抽象状态类的子类。
  • 优点:封装了转换规则,并枚举可能的状态,它将所有与某个状态有关的行为放到一个类中;可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为;可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
  • 缺点:使用状态模式会增加系统类和对象的个数,且状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切换状态的状态模式不满足“开闭原则”的要求。
  • 场景:
    • 对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为;
    • 代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。

2.7 模板方法模式

Template Method Pattern

  • 定义:定义算法框架,并将一些步骤的实现延迟到子类。通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。
  • 角色:关键代码在抽象类实现,其他步骤在子类实现。
  • 优点:封装不变部分,扩展可变部分;提取公共代码,便于维护;行为由父类控制,子类实现。
  • 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
  • 场景:
    • 有多个子类共有的方法,且逻辑相同;
    • 重要的、复杂的方法,可以考虑作为模板方法。

2.8 中介者模式

Mediator Pattern

  • 定义:集中相关对象之间复杂的沟通和控制方式。
  • 角色:Mediator中介者,定义一个接口用于与各同事(Colleague)对象通信。Colleague:同事,相关对象。
  • 优点:简化了对象之间的交互,将各同事解耦;可以减少子类生成,对于复杂的对象之间的交互,通过引入中介者;以简化各同事类的设计和实现。
  • 缺点:具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
  • 场景:
    • 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解;
    • 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象;
    • 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

2.9 备忘录模式

Memento Pattern

  • 定义:保存一个对象的某个状态,以便在适当的时候恢复对象。在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。
  • 角色:Originator原始对象,Caretaker负责保存好备忘录,Memento备忘录,存储原始对象的的状态。客户不与备忘录类耦合,与备忘录管理类耦合。
  • 优点:给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态;实现了信息的封装,使得用户不需要关心状态的保存细节。
  • 缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
  • 场景:
    • 需要保存/恢复数据的相关状态场景;
    • 提供一个可回滚的操作。

2.10 解释器模式

Interpreter Pattern

  • 定义:为语言创建解释器,通常由语言的语法和语法分析来定义。
  • 角色:构建语法树,定义终结符与非终结符。Context上下文,包含解释器之外的一些全局信息。
  • 优点:可扩展性比较好,灵活;增加了新的解释表达式的方式;易于实现简单文法。
  • 缺点:可利用场景比较少;对于复杂的文法比较难维护;解释器模式会引起类膨胀;解释器模式采用递归调用方法。
  • 场景:
    • 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树;
    • 一些重复出现的问题可以用一种简单的语言来进行表达;
    • 一个简单语法需要解释的场景。

2.11 访问者模式

Visitor Pattern

  • 定义:为一个对象结构(比如组合结构)增加新能力。
  • 角色:使用了一个访问者类,改变元素类的执行算法。元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
  • 优点:符合单一职责原则;优秀的扩展性;灵活性。
  • 缺点:具体元素对访问者公布细节,违反了迪米特原则;具体元素变更比较困难;违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
  • 场景:
    • 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作;
    • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,也不希望在增加新操作时修改这些类。

3 结构型

结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。

3.1 外观模式

Facade Pattern

  • 定义:提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。
  • 角色:外观角色是在客户端直接调用的角色,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任。
  • 优点:对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易;它实现了子系统与客户之间的松耦合关系,并降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程。
  • 缺点:不能很好地限制客户使用子系统类,而且在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
  • 场景:
    • 要为一个复杂子系统提供一个简单接口;客户程序与多个子系统之间存在很大的依赖性;
    • 在层次化结构中,需要定义系统中每一层的入口,使得层与层之间不直接产生联系。

3.2 适配器模式

Adapter Pattern

  • 定义:把一个类接口转换成另一个用户需要的接口。作为两个不兼容的接口之间的桥梁。
  • 角色:目标抽象类定义客户要用的特定领域的接口;适配器类可以调用另一个接口,作为一个转换器,对适配者和抽象目标类进行适配,它是适配器模式的核心;适配者类是被适配的角色,它定义了一个已经存在的接口,这个接口需要适配;在客户类中针对目标抽象类进行编程,调用在目标抽象类中定义的业务方法。
  • 优点:将目标类和适配者类解耦,增加了类的透明性和复用性,同时系统的灵活性和扩展性都非常好,更换适配器或者增加新的适配器都非常方便,符合“开闭原则”。
  • 缺点:在很多编程语言中不能同时适配多个适配者类,对象适配器模式的缺点是很难置换适配者类的方法。
  • 场景:
    • 系统需要使用现有的类,而这些类的接口不符合系统的需要;
    • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类一起工作。

3.3 组合模式

Composite Pattern

  • 定义:将对象组合成树形结构来表示“整体/部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。
  • 角色:组件(Component)类是组合类(Composite)和叶子类(Leaf)的父类,可以把组合类看成是树的中间节点。组合对象拥有一个或者多个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象。
  • 优点:高层模块调用简单;节点自由增加。
  • 缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
  • 场景:部分、整体场景,如树形菜单,文件、文件夹的管理。

3.4 代理模式

Proxy Pattern

  • 定义:一个类代表另一个类的功能。创建具有现有对象的对象,以便向外界提供功能接口。
  • 角色:抽象主题角色声明了真实主题和代理主题的共同接口;代理主题角色内部包含对真实主题的引用;真实主题角色定义了代理角色所代表的真实对象。
  • 优点:能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 缺点:由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,并且实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
  • 场景:1、远程代理。2、虚拟代理。3、Copy-on-Write 代理。4、保护(Protect or Access)代理。5、Cache代理。6、防火墙(Firewall)代理。7、同步化(Synchronization)代理。8、智能引用(Smart Reference)代理。

3.5 桥接模式

Bridge Pattern

  • 定义:将抽象与实现分离开来,使它们可以独立变化。提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
  • 角色:设置桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
  • 优点:分离抽象接口及其实现部分,是比多继承方案更好的解决方法;提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,实现细节对客户透明,可以对用户隐藏实现细节
  • 缺点:增加系统的理解与设计难度,且识别出系统中两个独立变化的维度并不是一件容易的事情。
  • 场景:
    • 需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系;
    • 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响;
    • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展;
    • 设计要求需要独立管理抽象化角色和具体化角色;不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统。

3.6 装饰模式

Decorator Pattern

  • 定义:向一个现有的对象添加新的功能,同时又不改变其结构。
  • 角色:装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。
  • 优点:可以提供比继承更多的灵活性,可以通过一种动态的方式来扩展一个对象的功能,并通过使用不同的具体装饰类以及这些装饰类的排列组合;可以创造出很多不同行为的组合,而且具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类。
  • 缺点:进行系统设计时将产生很多小对象,而且装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
  • 场景:
    • 在不影响其他对象的情况下,以动态、透明的方式给 单个对象添加职责;
    • 需要动态地给一个对象增加功能,这些功能也可以动态地 被撤销;
    • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展 和维护时。

3.7 享元模式

Flyweight Pattern

  • 定义:利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。
  • 角色:具体享元类实现了抽象享元接口,其实例称为享元对象;非共享具体享元是不能被共享的抽象享元类的子类;享元工厂类用于创建并管理享元对象。
  • 优点:可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
  • 缺点:使得系统更加复杂,并且需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
  • 场景:
    • 一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费;
    • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中;
    • 多次重复使用享元对象。
⤧  Next post 【Java】Java 04 Java虚拟机 ⤧  Previous post 【Java】Jee 02 SSM示例