一文读懂七大设计原则及GoF 23种设计模式

1、设计原则

(1)开闭原则(Open-Close Principle,OCP)
  • 定义:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节。
  • 优点:提高软件系统的可复用性及可维护性。
(2)依赖倒置原则(Dependence Inversion Principle,DIP)
  • 定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。针对接口编程,不要针对实现编程。
  • 优点:可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险。
(3)单一职责原则(Simple Responsibility Principle,SRP)
  • 定义:不要存在多于一个导致类变更的原因。一个类、接口、方法只负责一项职责。
  • 优点:降低类的复杂度。提高类的可读性。提高系统的可维护性。降低变更引起的风险。
(4)接口隔离原则(Interface Segregation Principle,ISP)
  • 定义:用多个专门的接口,而不是使用单一的总接口,客户端不应该依赖它不需要的接口。
  • 注意:一个类对应一个类的依赖应该建立在最小的接口上。建立单一接口,不要建立庞大臃肿的解耦。尽量细化接口,接口中的方法尽量少。注意适度原则,一定要适度。
(5)迪米特法则(Law of Demeter,LoD)
  • 定义:一个对象应该对其他对象保持最少的了解,又叫最少知道原则,尽量降低类与类之间的耦合。
  • 优点:降低类之间的耦合。
(6)里氏替换原则(Liskov Substitution Principle,LSP)
  • 定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
  • 定义扩展:一个软件实体如果适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
  • 引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能。
    含义1:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
    含义2:子类中可以增加自己特有的方法。
    含义3:当子类的方法重载父类的方法时,方法的前置条件(即方法的输入、入参)要比父类方法的输入参数更宽松。
    含义4:当子类的方法实现父类的方法时(重写、重载或实现抽象方法),方法的后置条件(即方法的输出、返回值)要比父类更严格或相等。
  • 优点:约束继承泛滥,开闭原则的一种体现。加强程序的健壮性,同时变更时也可以做到非常好的兼容性。提高程序的维护性、扩展性,降低需求变更时引入的风险。
(7)合成(组合)、聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
  • 定义:尽量使用对象组合、聚合,而不是继承关系达到软件复用的目的。聚合has-a和组合contains-a。
  • 优点:可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。
一句话归纳七大设计原则:
设计原则 一句话归纳 目的
开闭原则(OCP) 对扩展开放,对修改关闭 减少维护带来新的风险
依赖倒置原则(DIP) 高层不应该依赖底层,尽可能的依赖抽象 更利于代码结构的升级扩展
单一职责原则(SRP) 一个类只干一件事 便于理解,提高代码可读性
接口隔离原则(ISP) 一个接口只干一件事 功能解耦,高聚合、低耦合
迪米特法则(LoD) 不该知道的不要知道 只和朋友交流,不和陌生人说话,减少代码臃肿
里氏替换原则(LSP) 子类重写方法的功能发生改变,不应该影响父类方法的含义 防止继承泛滥
合成复用原则(CARP) 尽量使用组合实现代码复用,而不使用继承 降低代码耦合

2、GoF 23种设计模式简介

GoF 23种设计模式来源于《Design Patterns: Elements of Reusable Object-Oriented Software》,即后述《设计模式》一书。这本书由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides合著(Addison-Wesley,1995)。这几位作者常被称为“四人组(Gang of Four)”,而这本书也就被称为“四人组(或GoF)”书。

3、创建型模式(Creational Patterns)

(1)简单工厂模式(Simple Factory)
  • 定义:简单工厂模式(Simple Factory)是指由一个工厂对象决定创建出哪一种产品类的实例。
  • 简单工厂模式属于创建型模式,但不属于GoF 23种设计模式之一。
  • 简单工厂模式的适用场景:
  1. 工厂类负责创建的对象较少。
  2. 客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心。
  • 简单工厂模式的优点:
  1. 只需传入一个正确的参数,就可以获取你所需要的对象。
  2. 无需知道其创建的细节。
  • 简单工厂模式的缺点:
  1. 工厂类的职责相对过重,增加新的产品时需要修改工厂类的判断逻辑,违背开闭原则。
  2. 不易于扩展过于复杂的产品结构。
(2)工厂方法模式(Factory Method)
  • 定义:工厂方法模式(Factory Method)是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。
  • 工厂方法模式属于创建型模式。
  • 工厂方法模式的适用场景:
  1. 创建对象需要大量重复的代码。
  2. 客户端(应用层)不依赖产品类实例如何被创建、实现等细节。
  3. 一个类通过其子类来指定创建哪个对象。
  • 工厂方法模式的优点:
  1. 用户只需关心所需产品对应的工厂,无需关心创建细节。
  2. 加入新产品符合开闭原则,提高了系统的可扩展性。
  • 工厂方法模式的缺点:
  1. 类的个数容易过多,增加了代码结构的复杂度。
  2. 增加了系统的抽象性和理解难度。
(3)抽象工厂模式(Abstract Factory)
  • 定义:抽象工厂模式(Abstract Factory)是指提供一个创建一系列相关或相互依赖对象的接口,无需指定他们具体的类。
  • 产品族:一系列相关的产品,整合到一起有关联性。
  • 产品等级:同一个继承体系的产品。
  • 抽象工厂模式属于创建型模式。
  • 抽象工厂模式的适用场景:
  1. 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
  2. 强调一系列相关的产品对象(属于同一产品族)一起使用,创建对象需要大量重复的代码。
  3. 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
  • 抽象工厂模式的优点:
  1. 具体产品在应用层代码隔离,无需关心创建细节。
  2. 将一个系列的产品族统一到一起创建。
  • 抽象工厂模式的缺点:
  1. 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
  2. 增加了系统的抽象性和理解难度。
(4)建造者模式(Builder)
  • 定义:建造者模式(Builder Pattern)是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
  • 特征:用户只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。
  • 建造者模式属于创建型模式。
  • 建造者模式的适用场景:
  1. 适用于创建对象需要很多步骤,但是步骤的顺序不一定固定。
  2. 如果一个对象有非常复杂的内部结构(有很多属性)。
  3. 把复杂对象的创建和使用分离。
  • 建造者模式的优点:
  1. 封装性好,创建和使用分离。
  2. 扩展性好,建造类之间独立,一定程度上解耦。
  • 建造者模式的缺点:
  1. 产生多余的Builder对象。
  2. 产品内部发生变化,建造者都要修改,成本较大。
(5)单例模式(Singleton)
  • 定义:单例模式(Singleton)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
  • 单例模式隐藏其所有的构造方法。
  • 单例模式属于创建型模式。
  • 单例模式的适用场景:
  1. 确保任何情况下都绝对只有一个实例。
  2. ServletContext、ServletConfig、ApplicationContext、DBPool。
  • 单例模式的常见写法:
  1. 饿汉式单例
    优点:执行效率高,性能高,没有任何的锁。
    缺点:某些情况下,可能会造成内存浪费。
  2. 懒汉式单例
    优点:节省了内存。
    缺点:线程不安全。
    运行结果为同一个实例有两种情况:1. 两个线程以正常顺序执行(即正常的单例模式),2. 两个线程后者覆盖前者的情况。
    运行结果为不同的实例的情况:同时进入if(instance == null)里面,按顺序返回。
    加上synchronized关键字后,优点:节省了内存,线程安全;缺点:性能低。
    解决方法:被外部类调用时才创建实例。
  3. 容器式单例
    解决大批量创建对象的问题。
  4. 注册式单例
    将每一个实例都缓存到统一的容器中,使用唯一标识获取实例。
    枚举式单例是注册式单例的一种。
  5. ThreadLocal单例
  6. 序列化与反序列化
    序列化(持久化):把内存中对象的状态转换为字节码的形式,然后把字节码通过IO输出流写到磁盘上,这样就把内存中对象的状态永久的保存下来。
    反序列化(反持久化):将持久化的字节码内容通过IO输入流读到内存中来,转化成一个Java对象。
  • 单例模式的优点:
  1. 在内存中只有一个实例,减少了内存开销。
  2. 可以避免对资源的多重占用。
  3. 设置全局访问点,严格控制访问。
  • 单例模式的缺点:
  1. 没有接口,扩展困难。
  2. 如果要扩展单例对象,只有修改代码,没有其他途径。
  • 学习单例模式的知识重点总结:
  1. 私有化构造器。
  2. 保证线程安全。
  3. 延迟加载。
  4. 防止序列化和反序列化破坏单例。
  5. 防御反射攻击单例。
(6)原型模式(Prototype)
  • 定义:原型模式(Prototype)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
  • 原型模式中调用者不需要知道任何创建细节,不调用构造函数。
  • 原型模式属于创建型模式。
  • 原型模式的适用场景:
  1. 类初始化消耗资源较多。
  2. new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)。
  3. 构造函数比较复杂。
  4. 循环体中生产大量对象时。
  • 原型模式的优点:
  1. 性能优良,Java自带的原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多。
  2. 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建过程。
  • 原型模式的缺点:
  1. 必须配备克隆(或者可拷贝)方法。
  2. 当对已有类进行改造的时候,需要修改代码,违反了开闭原则。
  3. 深拷贝、浅拷贝需要运用得当。
一句话归纳创建型设计模式:
设计模式 一句话归纳 目的 生活案例 框架源码举例
工厂模式(Factory) 产品标准化,生产更高效 封装创建细节 实体工厂 LoggerFactory、Calender
单例模式(Singleton) 只有一个对象 保证独一无二 CEO BeanFactory、Runtime
原型模式(Prototype) 拔一根猴毛,吹出千万个 高效创建对象 克隆 ArrayList、PrototypeBean
建造者模式(Builder) 高配中配与低配,想选哪配就哪配 开放个性配置步骤 选配 StringBuilder、BeanDefinationBuilder

4、结构型模式(Structural Patterns)

(7)适配器模式(Adapter)
  • 适配器模式(Adapter Pattern)又叫做变压器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作。
  • 适配器模式属于结构型模式。
  • 适配器模式的适用场景:
  1. 已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况。
  2. 适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。
  3. 适配器模式主要是解决兼容问题。
  • 适配器模式的优点:
  1. 能提高类的透明性和复用性,现有的类复用但不需要改变。
  2. 目标类和适配器类解耦,提高程序的扩展性。
  3. 在很多业务场景中符合开闭原则。
  • 适配器模式的缺点:
  1. 适配器编写过程需要全面考虑,可能会增加系统的复杂性。
  2. 增加代码阅读难度,降低代码的可读性,过多使用适配器会使系统代码变得凌乱。
(8)装饰器模式(Decorator)
  • 装饰器模式(Decorator Pattern)也叫包装器模式(Wrapper Pattern),是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),装饰器可以透明地动态地扩展一个类的功能。
  • 装饰器模式属于结构型模式。
  • 装饰器模式的适用场景:
  1. 用于扩展一个类的功能或给一个类添加附加职责。
  2. 动态的给一个对象添加功能,这些功能可以再动态的撤销。
  • 装饰器模式和代理模式对比:
  1. 装饰器模式就是一种特殊的代理模式。
  2. 装饰器模式强调自身的功能扩展,用户自己说了算的透明可动态定制的扩展。
  3. 代理模式强调代理过程的控制。
  • 装饰器模式的优点:
  1. 装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
  2. 通过使用不同装饰类以及这些装饰类的排列组合,可实现不同效果。
  3. 装饰器完全遵守开闭原则。
  • 装饰器模式的缺点:
  1. 会出现更多的代码,更多的类,增加程序的复杂性。
  2. 动态装饰时,多层装饰时,会更复杂。
(9)代理模式(Proxy)
  • 代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问,代理对象在客户端和目标对象之间起到中介作用。
  • 代理模式属于结构型模式。
  • 代理模式的适用场景:
  1. 保护目标对象。
  2. 增强目标对象。
  • 代理模式的优点:
  1. 代理模式能将代理对象与真实被调用的目标对象分离,一定程度上降低了系统的耦合度,易于扩展。
  2. 代理可以起到保护目标对象的作用。
  3. 代理增强了目标对象的职责。
  • 代理模式的缺点:
  1. 代理模式会造成系统设计中类的数目的增加。
  2. 在客户端和目标对象之间增加了一个代理对象,请求处理速度变慢。
  3. 增加了系统的复杂度。
  • Spring中的代理选择原则:
  1. 当Bean有实现接口时,Spring就会用JDK的动态代理。
  2. 当Bean没有实现接口时,Spring选择CGLib。
  3. Spring可以通过配置强制使用CGLib,只需在Spring的配置文件中加入如下代码:

  • 静态代理和动态代理的根本区别:
  1. 静态代理:硬编码,手动注入,手动拿到目标对象的引用,手动调用代理目标的方法。
  2. 动态代理:具有更强的扩展性,自动注入,自动生成一个新的类(同一个继承体系)。
  3. 静态代理和动态代理共同具有的特征:拿到代理目标对象的引用,实现功能增强,保护目标对象。
(10)门面模式(Facade)
  • 门面模式(Facade Pattern)又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。
  • 特征:门面模式定义了一个高层接口,让子系统更容易使用。
  • 门面模式属于结构型模式。
  • 门面模式的适用场景:
  1. 子系统越来越复杂,增加门面模式提供简单接口。
  2. 构建多层系统结构,利用门面对象作为每层的入口,简化层间调用。
  • 门面模式的优点:
  1. 简化了调用过程,无需深入了解子系统,以防给子系统带来风险。
  2. 减少系统依赖、松散耦合。
  3. 更好地划分访问层次,提高了安全性。
  4. 遵循迪米特法则,即最少知道原则。
  • 门面模式的缺点:
  1. 当增加子系统和扩展子系统行为时,可能容易带来未知风险。
  2. 不符合开闭原则。
  3. 某些情况下可能违背单一职责原则。
  • 门面模式与代理模式:
  1. 门面模式就是特殊的静态代理模式。
  2. 门面模式:重点在于封装。
  3. 静态代理:重点在于增强。
  4. 不做增强的静态代理就是门面模式。
  • 委派模式与代理模式:
  1. 委派模式也是一种静态代理,委派模式不属于23种设计模式。
  2. 代理模式:结构型模式。
  3. 委派模式:行为型模式。
  • 门面模式与单例模式:
  1. 门面模式做成单例,如将工具包做成门面模式。
(11)组合模式(Composite)
  • 组合模式(Composite Pattern)也称为整体-部分(Part-Whole)模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示。
  • 作用:使客户端对单个对象和组合对象保持一致的处理方式。
  • 组合模式属于结构型模式。
  • 组合模式的适用场景:
  1. 希望客户端可以忽略组合对象与单个对象的差异时。
  2. 对象层次具备整体和部分,呈树型结构(如树型菜单,操作系统目录结构,公司组织架构等)。
  3. 不管你是整体还是个体,都要想办法用同一种方法来操作。
  • 组合模式的优点:
  1. 清楚地定义分层次的复杂对象,表示对象的全部或部分层次。
  2. 让客户忽略了层次的差异,方便对整个层次结构进行控制。
  3. 简化客户端代码。
  4. 符合开闭原则。
  • 组合模式的缺点:
  1. 限制类型时会较为复杂。
  2. 使设计变得更加抽象。
(12)享元模式(Flyweight)
  • 享元模式(Flyweight Pattern)又称为轻量级模式,是对象池的一种实现。类似于线程池,线程池可以避免不停的创建和销毁多个对象而消耗性能。
  • 享元模式提供了减少对象数量从而改善应用所需的对象结构的方式。
  • 宗旨:共享细粒度对象,将多个对同一对象的访问集中起来。
  • 享元模式属于结构型模式。
  • 享元模式的适用场景:
  1. 常常应用于系统底层的开发,以便解决系统的性能问题。
  2. 系统有大量相似对象、需要缓冲池的场景。
  • 享元模式分为内部状态和外部状态。
  • 享元模式的优点:
  1. 减少对象的创建,降低内存中对象的数量,降低系统的内存,提高效率。
  2. 减少内存之外的其他资源的占用。
  • 享元模式的缺点:
  1. 需要关注内部状态和外部状态,关注线程安全问题。
  2. 使系统、程序的逻辑复杂化。
  • 享元模式一般设置成享元工厂,而享元工厂大部分情况设置成单例,因为缓存存放在一个地方。
(13)桥接模式(Bridge)
  • 桥接模式(Bridge Pattern)也称为桥梁模式、接口(Interface)模式或柄体(Handle and Body)模式,是将抽象部分与它的具体实现部分分离,使它们都可以独立地变化。
  • 桥接模式通过组合的方式建立两个类之间的联系,而不是继承。
  • 桥接模式属于结构型模式。
  • 桥接模式的适用场景:
  1. 在抽象和具体实现之间需要增加更多的灵活性的场景。
  2. 一个类存在两个或多个独立变化的维度,而这两个或多个维度都需要独立进行扩展。
  3. 不希望使用继承,或因为多层继承导致系统类的个数剧增。
  • 桥接模式的优点:
  1. 分离抽象部分及其具体实现部分。
  2. 提高了系统的扩展性。
  3. 符合开闭原则。
  4. 符合合成复用原则。
  • 桥接模式的缺点:
  1. 增加了系统的理解与设计难度。
  2. 需要正确地识别系统中两个独立变化的维度。
一句话归纳结构型设计模式:
设计模式 一句话归纳 目的 生活案例 框架源码举例
代理模式(Proxy) 没有资源没时间,得找媒婆来帮忙 增强职责 媒婆 ProxyFactoryBean、JdkDynamicAopProxy、CglibAopProxy
门面模式(Facade) 打开一扇门,走向全世界 统一访问入口 前台、导诊台 JdbcUtils、RequestFacade
装饰器模式(Decorator) 他大舅他二舅都是他舅 灵活扩展、同宗同源 煎饼 BufferedReader、InputStream
享元模式(Flyweight) 优化资源配置,减少重复浪费,开源节流 共享资源池 全国社保联网 String、Integer、ObjectPool
组合模式(Composite) 人在一起叫团伙(聚合),心在一起叫团队(组合) 统一整体和个体 组织架构树 HashMap、SqlNode
适配器模式(Adapter) 适合自己的,才是最好的 兼容转换 电源适配 AdvisorAdapter(Spring AoP)、HandlerAdapter(SpringMVC)
桥接模式(Bridge) 从现实连接抽象 不允许用继承 DriverManager

5、行为型模式(Behavioral Patterns)

(14)委派模式(Delegate)
  • 委派模式(Delegate Pattern)又叫委托模式。它的基本作用就是负责任务的调度和任务分配,将任务的分配和执行分离开来,可以看做是一种特殊情况下的静态代理的全权代理。
  • 委派模式不属于GOF 23种设计模式之一。
  • 委派模式属于行为型模式。
  • 委派模式的应用场景:
  1. 委派对象本身不知道如何处理一个任务(或一个请求),把请求交给其他对象来处理。
  2. 实现程序的解耦。
  • 委派模式的优点:
  1. 通过任务委派能将一个大型的任务细化,然后通过统一管理这些子任务的完成情况实现任务的跟进,能够加快任务执行的效率。
  • 委派模式的缺点:
  1. 任务委派方式需要根据任务的复杂程度进行不同的改变,在任务比较复杂的情况下可能需要进行多重委派,容易造成紊乱。
  • 委派模式和代理模式的区别:
  1. 委派模式是行为型模式,代理模式是结构型模式。
  2. 委派模式注重的是任务派遣,注重结果;代理模式注重的是代码增强,注重过程。
  3. 委派模式是一种特殊的静态代理,相当于全权代理。
(15)策略模式(Strategy)
  • 策略模式(Strategy Pattern)也叫政策模式(Policy Pattern),它是将定义的算法家族分别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。
  • 策略模式可以避免多重分支的if…else…和switch语句。
  • 策略模式属于行为型模式。
  • 策略模式的适用场景:
  1. 假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。
  2. 一个系统需要动态地在几种算法中选择一种。
  3. 需要屏蔽算法规则。
  • 策略模式的优点:
  1. 策略模式符合开闭原则。
  2. 避免使用多重条件转移语句,如if…else…语句、switch语句。
  3. 使用策略模式可以提高算法的保密性和安全性。
  • 策略模式的缺点:
  1. 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
  2. 代码中会产生非常多的策略类,增加维护难度。
(16)模板方法模式(Template Method)
  • 模板方法模式(Template Method Pattern)通常又叫模板模式,是指定义一个算法的骨架,并允许子类为其中的一个或者多个步骤提供实现。
  • 模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
  • 模板方法模式属于行为型设计模式。
  • 模板方法模式的适用场景:
  1. 一次性实现一个算法不变的部分,并将可变的行为留给子类来实现。
  2. 各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。
  • 模板方法模式的优点:
  1. 利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。
  2. 将不同的代码放到不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性。
  3. 把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。
  • 模板方法模式的缺点:
  1. 类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。
  2. 类数量的增加,间接地增加了系统实现的复杂度。
  3. 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。
  • 模板方法模式与其他模式的对比:
  1. 模板方法模式:只能通过继承来实现。
  2. 桥接模式:只要你不用继承,啥都行。
  3. 装饰器模式:继承+组合(同宗同源)。
  4. 代理模式:不一定用继承,CGLib通过继承来实现代理。
(17)观察者模式(Observer)
  • 观察者模式(Observer Pattern)又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。定义一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听,使得每当主题对象状态变化时,所有依赖于它的对象都会得到通知并被自动更新。
  • 观察者模式的核心:将观察者与被观察者解耦。
  • 观察者模式属于行为型模式。
  • 观察者模式的适用场景:
  1. 当一个抽象模型包含两个方面内容,其中一个方面依赖于另一个方面。
  2. 其他一个或多个对象的变化依赖于另一个对象的变化。
  3. 实现类似广播机制的功能,无需知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
  4. 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
  • 观察者模式的优点:
  1. 观察者和被观察者是松耦合(抽象耦合)的,符合依赖倒置原则。
  2. 分离了表示层(观察者)和数据逻辑层(被观察者),并且建立了一套触发机制,使得数据的变化可以响应到多个表示层上。
  3. 实现了一对多的通讯机制,支持事件注册机制,支持兴趣分发机制,当被观察者触发事件时,只有感兴趣的观察者可以接收到通知。
  • 观察者模式的缺点:
  1. 如果观察者数量过多,则事件通知会耗时较长。
  2. 事件通知呈线性关系,如果其中一个观察者处理事件卡壳,会影响后续的观察者接收该事件。
  3. 如果观察者和被观察者之间存在循环依赖,则可能造成两者之间的循环调用,导致系统崩溃。
(18)迭代器模式(Iterator)
  • 迭代器模式(Iterator Pattern)又称为游标模式(Cursor Pattern),它提供一种顺序访问集合、容器对象元素的方法,而又无须暴露集合内部表示。
  • 迭代器模式的本质:抽离集合对象迭代行为到迭代器中,提供一致访问接口。
  • 迭代器模式属于行为型模式。
  • 迭代器模式的适用场景:
  1. 访问一个集合对象的内容而无需暴露它的内部表示。
  2. 为遍历不同的集合结构提供一个统一的访问接口。
  • 迭代器模式的优点:
  1. 多态迭代:为不同的聚合结构提供一致的遍历接口,即一个迭代接口可以访问不同的聚集对象。
  2. 简化集合对象接口:迭代器模式将集合对象本身应该提供的元素迭代接口抽取到了迭代器中,使集合对象无须关心具体迭代行为。
  3. 元素迭代功能多样化:每个集合对象都可以提供一个或多个不同的迭代器,使得同种元素聚合结构可以有不同的迭代行为。
  4. 解耦迭代与集合:迭代器模式封装了具体的迭代算法,迭代算法的变化不会影响到集合对象的架构。
  • 迭代器模式的缺点:
  1. 对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐。
  • E、T、K、V、?
  1. E:Element,在集合中规定集合元素的类型。
  2. T:Type,规定该类操作的类的具体类型。
  3. K:Key,规定Key的类型。
  4. V:Value,规定Value的类型。
  5. ?:任意类型,都是Object。
  6. :通配符,必须是IMember的子类。
(19)责任链模式(Chain of Responsibility)
  • 责任链模式(Chain of Responsibility Pattern)是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。
  • 当一个请求从链的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止。
  • 责任链模式属于行为型模式。
  • 责任链模式的适用场景:
  1. 多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态决定。
  2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  3. 可动态指定一组对象处理请求。
  • 责任链模式的优点:
  1. 将请求与处理解耦。
  2. 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一级节点对象。
  3. 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。
  4. 链路结构灵活,可以通过改变链路结构动态地新增或删减责任。
  5. 易于扩展新的请求处理类(节点),符合开闭原则。
  • 责任链模式的缺点:
  1. 责任链太长或者处理时间过长,会影响整体性能。
  2. 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。
  • 权限安全校验框架中,大量使用责任链,它真正实现了权限校验的解耦,比如Spring Security,Apache Shiro。
(20)中介者模式(Mediator)
  • 中介者模式(Mediator Pattern)又称为调解者模式或调停者模式。用一个中介对象封装一系列的对象交互,中介者使各对象不需要显式地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
  • 中介者模式的核心:通过中介者解耦系统各层次对象的直接耦合,层次对象的对外依赖通信统统交由中介者转发。
  • 中介者模式属于行为型模式。
  • 中介者模式的适用场景:
  1. 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
  2. 交互的公共行为,如果需要改变行为则可以增加新的中介者类。
  • 中介者模式和代理模式的区别:
  1. 都有一个中间角色,牵线搭桥的作用。
  2. 为什么要代理?因为你做不到,又必须要做。不仅帮你联系到,而且帮你做一些事情。
  3. 中介者:只要他帮你联系上,就不管了,后面的事情全权由自己完成。
  • 中介者模式和门面模式的区别:
  1. 中介者模式是行为型模式,门面模式是结构型模式。
  2. 中介者模式不需要将其他类的对象放到自己的类中,直接存到数据库中都可以。
  • 中介者模式和桥接模式的区别:
  1. 桥接模式是二路桥接。
  2. 中介者模式是多路桥接。
  • 中介者模式的优点:
  1. 减少类间依赖,将多对多依赖转化成了一对多,降低了类间耦合。
  2. 类间各司其职,符合迪米特法则。
  • 中介者模式的缺点:
  1. 中介者模式中将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。
  2. 当同事类越多时,中介类就会越臃肿,变得复杂且难以维护。
(21)解释器模式(Interpreter)
  • 解释器模式(Interpreter Pattern)给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
  • 解释器模式的特征:为了解释一种语言,而为语言创建的解释器。
  • 解释器模式属于行为型模式。
  • 解释器模式的适用场景:
  1. 一些重复出现的问题可以用一种简单的语言来进行表达。
  2. 一个简单语法需要解释的场景。
  • 解释器模式的优点:
  1. 扩展性强:在解释器模式中由于语法是由很多类表示的,当语法规则更改时,只需修改相应的非终结表达式即可。若扩展语法时,只需添加相应非终结符类即可。
  2. 增加了新的解释表达式的方式。
  3. 易于实现文法:解释器模式对应的文法应当是比较简单且易于实现的,过于复杂的语法并不适合使用解释器模式。
  • 解释器模式的缺点:
  1. 语法规则较复杂时,会引起类膨胀。
  2. 执行效率比较低。
(22)命令模式(Command)
  • 命令模式(Command Pattern)是对命令的封装,每一个命令都是一个操作:请求的一方发出请求要求执行一个操作,接收的一方收到请求,并执行操作。
  • 命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎样被接收,怎样被操作以及是否被执行等等。
  • 命令模式的本质:解耦命令的请求与处理。
  • 命令模式属于行为型模式。
  • 命令模式的适用场景:
  1. 现实语义中具备“命令”的操作(如命令菜单,shell命令…)。
  2. 请求调用者和请求接收者需要解耦,使得调用者和接收者不直接交互。
  3. 需要抽象出等待执行的行为,比如撤销(Undo)操作和恢复(Redo)等操作。
  4. 需要支持命令宏(即命令组合操作)。
  • 命令模式的优点:
  1. 通过引入中间件(抽象接口),解耦了命令请求与实现。
  2. 扩展性良好,可以很容易地增加新命令。
  3. 支持组合命令,支持命令队列。
  4. 可以在现有命令的基础上,增加额外功能(比如日志记录,可以结合装饰器模式)。
  • 命令模式的缺点:
  1. 具体命令类可能过多。
  2. 增加了程序的复杂度,理解更加困难。
(23)状态模式(State)
  • 状态模式(State Pattern)也称为状态机模式(State Machine Pattern),是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
  • 状态模式属于行为型模式。
  • 状态模式的适用场景:
  1. 行为随状态改变而改变的场景。
  2. 一个操作中含有庞大的多分支结构,并且这些分支取决于对象的状态。
  • 状态模式在源码中的应用(在Maven中引入jar包):

    com.sun.faces
    jsf-api
    1.2

  • 状态模式相关的设计模式:
  1. 状态模式与责任链模式。
  2. 状态模式与策略模式。
  3. 状态模式与桥接模式(桥接模式:连接抽象维度和具象维度,但是不能用继承)。
  • 状态模式的优点:
  1. 结构清晰:将状态独立为类,消除了冗余的if…else或switch…case语句,使代码更加简洁,提高了系统的可维护性。
  2. 将状态转换显式化:通常的对象内部都是使用数值类型来定义状态,状态的切换是通过赋值进行表现,不够直观;而使用状态类,在切换状态时,是以不同的类进行表示,转换目的更加明确。
  3. 状态类职责明确且具备扩展性。
  • 状态模式的缺点:
  1. 类膨胀:如果一个事物具备很多状态,则会造成状态类太多。
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  3. 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新状态,而且修改某个状态类的行为也需要修改对应类的源代码。
(24)备忘录模式(Memento)
  • 备忘录模式(Memento Pattern)又称为快照模式(Snapshot Pattern)或令牌模式(Token Pattern),是指在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
  • 备忘录模式的特征:“后悔药”。
  • 备忘录模式属于行为型模式。
  • 备忘录模式的适用场景:
  1. 需要保存历史快照的场景。
  2. 希望在对象之外保存状态,且除了自己其他类对象无法访问状态保存的具体内容。
  • 备忘录模式的优点:
  1. 简化发起人实体类(Originator)职责,隔离状态存储与获取,实现了信息的封装,客户端无需关心状态的保存细节。
  2. 提供状态回滚功能。
  • 备忘录模式的缺点:
    1.消耗资源:如果需要保存的状态过多时,每一次保存都会消耗很多内存和磁盘。
(25)访问者模式(Visitor)
  • 访问者模式(Visitor Pattern)是一种将数据结构与数据操作分离的设计模式,是指封装一些作用于某种数据结构中的各元素的操作。
  • 访问者模式的特征:可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
  • 访问者模式属于行为型模式。
  • 访问者模式的适用场景:
  1. 数据结构稳定,作用于数据结构的操作经常变化的场景。
  2. 需要数据结构与数据操作分离的场景。
  3. 需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景。
  • 访问者模式的优点:
  1. 解耦了数据结构与数据操作,使得操作集合可以独立变化。
  2. 扩展性好,可以通过扩展访问者角色,实现对数据集的不同操作。
  3. 元素具体类型并非单一,访问者均可操作。
  4. 各角色职责分离,符合单一职责原则。
  • 访问者模式的缺点:
  1. 无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,则访问者必须增加对应元素类型的操作,违背了开闭原则。
  2. 具体元素变更困难:具体元素增加属性、删除属性等操作会导致对应的访问者类需要进行相应的修改,尤其当有大量访问者类时,修改范围太大。
  3. 违背依赖倒置原则:为了达到“ 区别对待”,访问者依赖的是具体元素类型,而不是抽象。
  • 访问者模式的精髓:
  1. 对原始数据只能访问,不能改变。
  2. 大部分情况下,用于数据报表和统计。
  3. 不同的访问者,得到不同的结果。
一句话归纳行为型设计模式:
设计模式 一句话归纳 目的 生活案例 框架源码举例
委派模式(Delegate) 这个需求很简单,怎么实现我不管 只对结果负责 授权委托书 ClassLoader、BeanDefinitionParserDelegate
模板模式(Template) 流程全部标准化,需要微调请覆盖 逻辑复用 把大象装进冰箱的步骤 JdbcTemplate、HttpServlet
策略模式(Strategy) 条条大道通罗马,具体哪条你来定 把选择权交给用户 选择支付方式 Comparator、InstantiationStrategy
责任链模式(Chain of Responsibility) 审批盖章即担责 解耦处理逻辑 踢皮球 FilterChain、Pipeline
迭代器模式(Iterator) 流水线上坐一天,每个包裹扫一遍 统一对集合的访问方式 统一刷脸进站 Iterator
命令模式(Command) 运筹帷幄之中,决胜千里之外 解耦请求和处理 遥控器 Runnable、TestCase
状态模式(State) 状态驱动行为,行为决定状态 绑定状态和行为 订单状态跟踪 Lifecycle
备忘录模式(Memento) 给我一剂“后悔药” 备份 草稿箱 StateManageableMessageContext
中介者模式(Mediator) 联系方式我给你,怎么搞定我不管 统一管理网状资源 朋友圈 Timer
解释器模式(Interpreter) 我想说“方言” 实现特定语法解析 摩斯密码 Pattern、ExpressionParser
观察者模式(Observer) 到点就通知我 解耦观察者与被观察者 闹钟 ContextLoaderListener
访问者模式(Visitor) 横看成岭侧成峰,远近高低各不同 解耦数据结构和数据操作 KPI考核 FileVisitor、BeanDefinitionVisitor

6、容易混淆的设计模式对比

(1)创建型模式vs.结构型模式vs.行为型模式
  • 创建型模式:对创建Java对象的形式提出的建议
  • 结构型模式:对Java类的组合提出的建议
  • 行为型模式:对Java中的方法的使用提出的建议
(2)委派模式vs.代理模式
  • 委派模式:全权的静态代理,不做任何的增强,没有任何功能增强的就叫委派模式
  • 代理模式:一定会有增强
(3)命令模式vs.策略模式
  • 命令模式:解耦请求和处理,会有一个回调,会有一个反馈和处理结果
  • 策略模式:固定好的选项,一定是同样的结果,一定要用户有参与
  • 共同点:都有一个清单,给你啥就是啥
(4)代理模式vs.装饰器模式
  • 代理模式:组合实现功能增强和扩展
  • 装饰器模式:继承实现增强和扩展
(5)委派模式vs.责任链模式
  • 委派模式:没有流程的概念,只需要拿到结果
  • 责任链模式:有处理流程,处理流程可扩展、可定制,最终结果由责任链中的某些Handler来决定
(6)代理模式vs.中介者模式
  • 代理模式:职责增强,不仅要建立联系,还要参与整个过程,要跟进
  • 中介者模式:只负责牵线搭桥,建立联系,只管联系上,不参与过程
(7)工厂方法vs.抽象工厂
  • 工厂方法:注重产品扩展,单向维度
  • 抽象工厂:注重产品等级结构和产品族的相互扩展关系,多维度,至少是二维
(8)桥接模式vs.适配器模式
  • 桥接模式:不用继承建立联系
  • 适配器模式:类适配器用的是继承,对象适配器用的是组合,接口适配器用的是继承
(9)抽象工厂vs.模板方法
  • 抽象工厂模式:形式上可能会出现抽象方法,但是并不能体现流程
  • 模板方法:也是有可能出现抽象方法,但是一定是某个流程中的一个步骤
(10)建造者模式vs.装饰器模式
  • 建造者模式:强调对象创建步骤的个性化,一般来说会有标配
  • 装饰器模式:更加强调扩展性,大桶套小桶
(11)适配器模式vs.中介者模式
  • 适配器模式:解决兼容问题,可以用继承,也可以用组合
  • 中介者模式:一定是用组合,所有人可能都持有中介者的引用
(12)桥接模式vs.中介者模式
  • 共同点:不用继承,一定是用组合
  • 桥接模式:两个维度建立连接
  • 中介者模式:多维度建立连接,是一个更复杂的桥接模式的实现
(13)桥接模式vs.组合模式
  • 桥接模式:两个继承体系建立连接,反而就是为了满足个性的
  • 组合模式:目的不是为了建立连接,而是为了统一行动,统一一套API
(14)门面模式vs.装饰器模式
  • 门面模式:打开一扇窗,通向全世界,统一入口,背后有很多个子系统,承担一定的静态代理作用
  • 装饰器模式:为了扩展,一定是同种同源的
(15)工厂模式vs.策略模式
  • 一般来说,他们两会组合使用,策略将由工厂来创建
(16)享元模式vs.饿汉式单例
  • 我们可以把对象池的容器设置成单例,同时,把对象池所在类设置为单例的工厂
(17)桥接模式vs.命令模式
  • 桥接模式:需要一个中间的类,一定是有功能实现的
  • 命令模式:需要一个抽象的中间类,只是为了规范
(18)委派模式vs.门面模式
  • 在门面模式里面,可能会用到委派模式实现任务分发
(19)适配器模式vs.策略模式
  • 适配器模式:为了兼容
  • 策略模式:把选择权交给用户
(20)策略模式vs.模板模式
  • 有时候会混用,模板模式中可能设计的钩子方法,就是某一个策略的实现,用某一个策略得到结果

你可能感兴趣的:(Design,Patterns)