Java 设计模式

文章目录

  • 设计模式
    • 一、UML
      • 1. UML 简介
      • 2. 类图
        • 2.1. 类鱼类图
        • 2.2. 类之间的关系
      • Plant UML 入门
        • 1. 关联关系
        • 2. 依赖关系
        • 3. 泛化关系(继承)
        • 4. 接口与实现关系
    • 二、设计原则
      • 1. 单一职责原则
        • 1.1. 定义
        • 1.2. 总结
      • 2. 接口隔离原则
        • 2.1. 定义
        • 2.2. 类图
      • 3. 依赖倒转原则
        • 3.1. 定义
        • 3.2. 分析
        • 3.3. 总结
      • 4. 里氏替换原则
        • 4.1. OO 中的继承性
        • 4.2. 定义
        • 4.3. 类图
      • 5. 开闭原则
        • 5.1. 定义
        • 5.2. 类图
      • 6. 迪米特法则
        • 6.1. 定义
        • 6.2. 总结
      • 7. 合成复用原则
        • 7.1. 定义
        • 7.2. 类图
    • 三、创建型模式
      • 1. 单例模式
        • 1.1. 定义
        • 1.2. java 中实现单例模式
          • 1.2.1. ⭐饿汉式(静态常量)
          • 1.2.2. ⭐饿汉式(静态代码块)
          • 1.2.3. 懒汉式(非线程安全)
          • 1.2.4. 懒汉式(线程安全,同步方法)
          • 1.2.5. 懒汉式(线程安全,同步代码块)
          • 1.2.6. ⭐双重检查
          • 1.2.7. ⭐静态内部类
          • 1.2.8. ⭐枚举
      • 2. 简单工厂模式
        • 2.1. 定义
        • 2.2. 模式结构
        • 2.3. 实例说明
      • 3. 工厂方法模式
        • 3.1. 定义
        • 3.2. 模式结构
        • 3.3. 实例说明
      • 4. 抽象工厂模式
        • 4.1. 定义
        • 4.2. 模式结构
        • 4.3. 实例说明
      • 5. 原型模式
        • 5.1. 定义
        • 5.2. 模式结构
        • 5.3. 深拷贝与浅拷贝
      • 6. 建造者模式
        • 6.1. 定义
        • 6.2. 模式结构
        • 6.3. 实例说明
    • 四、结构型模式
      • 1. 适配器模式
        • 1.1. 定义
        • 1.2. 模式结构
        • 1.3. 实例说明
          • 1.3.1. 电源适配器(类适配器模式)
          • 1.3.2. 电源适配器(对象适配器模式)
        • 1.4. 模式优缺点
        • 1.5. 缺省(接口)适配器模式
      • 2. 桥接模式
        • 2.1. 定义
        • 2.2. 模式结构
        • 2.3. 实例说明
        • 2.4. 模式优缺点
      • 3. 装饰模式
        • 3.1. 定义
        • 3.2. 模式结构
        • 3.3. 实例说明
        • 3.4. IO
        • 3.5. 模式优缺点
      • 4. 组合模式
        • 4.1. 定义
        • 4.2. 模式结构
        • 4.3. 模式优缺点
      • 5. 外观模式
        • 5.1. 定义
        • 5.2. 模式结构
        • 5.3. 模式优缺点
      • 6. 享元模式
        • 6.1. 定义
        • 6.2. 模式结构
        • 6.3. 模式优缺点
      • 7. 代理模式
        • 7.1. 定义
        • 7.2. 模式结构
        • 7.3. 模式优缺点
    • 五、行为型模式
      • 1. 模板方法模式
        • 1.1. 定义
        • 1.2. 模式结构
        • 1.3. 模式优缺点
      • 2. 命令模式
        • 2.1. 定义
        • 2.2. 模式结构
        • 2.3. 模式优缺点
      • 3. 访问者模式
        • 3.1. 定义
        • 3.2. 模式结构
        • 3.3. 实例说明
        • 3.4. 模式优缺点
      • 4. 迭代器模式
        • 4.1. 定义
        • 4.2. 模式结构
        • 4.3. 实例说明
          • 4.3.1. MyCollection
          • 4.3.2. MyIterator
          • 4.3.3. NewCollection
          • 4.3.4. MyObject
          • 4.3.5. Client
        • 4.4. 模式优缺点
      • 5. 观察者模式
        • 5.1. 定义
        • 5.2. 模式结构
        • 5.3. 实例说明
        • 5.4. 模式优缺点
      • 6. 中介者模式
        • 6.1. 定义
        • 6.2. 模式结构
        • 6.3. 模式优缺点
      • 7. 备忘录模式
        • 7.1. 定义
        • 7.2. 模式结构
        • 7.3. 实例说明
        • 7.4. 模式优缺点
      • 8. 解释器模式
        • 8.1. 定义
        • 8.2. 模式结构
        • 8.3. 实例说明
        • 8.4. 模式优缺点
      • 9. 状态模式
        • 9.1. 定义
        • 9.2. 模式结构
        • 9.3. 实例说明
        • 9.4. 模式优缺点
      • 10. 策略模式
        • 10.1. 定义
        • 10.2. 模式结构
        • 10.3. 实例说明
        • 10.4. 模式优缺点
      • 11. 职责链模式
        • 11.1. 定义
        • 11.2. 模式结构
        • 11.3. 实例说明
        • 11.4. 模式优缺点

设计模式

  1. 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是 某类问题的通用解决方案,设计模式(Design pattern)代表了最佳的实践;
  2. 设计模式的本质是提高 软件的维护性、通用性和拓展性,并降低软件的复杂度
  3. 设计模式不局限于某种语言;

一、UML

1. UML 简介

统一建模语言(Unified Modeling language,UML)是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果。

UML 本身是一套符号的规定,就像数学符号和化学符号一样,哲学符号用于描述软件模型中的各个元素和它们之间的关系,比如类、接口、实现、泛化、依赖、组合、聚合等;

使用 UML 建模,目前由许多工具,如在线网站 draw.io、processon,客户端工具 Rational Rose,IDE 插件如 IDEA 中的 PlantUML(本文中的类图均是用此插件绘制的),Eclipse 中的 AmaterasUML 等等;

UML 图分类:

  1. 用例图(use case)
  2. 静态结构图:类图、对象图、包图、组件图、部署图
  3. 动态行为图:交互图(时序图与协作图)、状态图、活动图

2. 类图

2.1. 类鱼类图

在 UML 类图中,类一般有三部分组成:

  1. 类名,每个单词首字母大写

  2. 属性(Attributes),表示方式:可见性 名称: 类型[ = 默认值]

    可见性对应符号:

    1. 公有(public):+
    2. 保护(protected):#
    3. 私有(private):-
    4. 包内可见性(package,Java 语言增加的):*
  3. 类的操作(Operation),表示方式:可见性 名称([参数列表])[: 返回值类型]

    可见性对应的符号与上述属性中相同,构造方法无返回值类型;

  4. ⭐内部类,Java 语言中允许出现内部类,所以有时候类图会有第四部分;

2.2. 类之间的关系
  1. 关联关系(Association),是类与类之间最常用的一种关系,它是一种结构化关系,用于表示一类对象与另一类对象之间有联系;关联关系有如下六种:

    1. 双向关联,可以有两个角色名,在 UML 中用直线表示;

    2. 单向关联,只有一个角色名,在 UML 中用直线加带箭头表示;

    3. 自关联,类的属性对象类型为该类本身,是单向关联的一种特例;

    4. 多重性关联,又称重数性关联关系(Multiplicity),表示一个类的对象与另一个类的对象连接的个数,在 UML 中可以直接在关联直线上增加一个数字表示与之对应的另一个类的对象个数;

      多重性表示方法列表(T = 表示另一个类的一个对象

      表示方式 多重性说明
      1..1 T 只与一个该类对象有关系
      0..* T 与零个或多个该类对象有关系
      1..* T 与一个或多个该类对象有关系
      0..1 T 没有或只与一个该类对象有关系
      m..n T 与最少 m、最多 n 个该类对象有关系(m ≤ n
    5. 聚合关系(Aggregation),表示一个整体与部分的关系,在 UML 中用带空心菱形的直线表示;

    6. 组合关系(Composition),表示类之间整体和部分的关系,组合关系中部分和整体具有统一的生命周期(生存期),在 UML 中用带实心菱形的直线表示;

  2. 依赖关系(Dependency),是一种使用关系,在 UML 中用带箭头的虚线表示

  3. 泛化关系(Generalization),即继承关系,也称为“is-a-kind-of”关系,在 UML 中用带空心三角形的直线表示;

  4. 接口与实现关系,接口之间也可以有继承关系和依赖关系,但是接口与类之间还存在实现关系(Realization),在 UML 中用带空心三角形的虚线表示;

Plant UML 入门

与其说 Plant UML 是画出来的,倒不如说它是写出来的,绘制的过程由插件自行绘制,用户只需要写出类的属性、操作等,以及类之间的关系,该插件将会自行绘制出一副可供阅读的 UML 类图;

可以 参考官网,以及 官方指南 阅读使用;

在 Plant UML 中:

  • -- 代表实线,- 的数量代表类图中该类距离另一个关系类的远近;

  • .. 代表虚线,. 的数量代表类图中该类距离另一个关系类的远近;

  • |><| 代表空心三角形,需与实线或虚线搭配使用表示 泛化关系实现关系;

  • o 代表空心菱形,需要与实线搭配使用代表 聚合关系

  • * 代表实心菱形,需要与实线搭配使用代表 组合关系

  • ' 代表注释,注释只有行级注释(单行)

  • interfaceabstractenumclass 分别代表声明接口、抽象类、枚举、类

  • note 笔记,类似于注释,用法如下:

    # 用法一:在一个类(类名为 ClassName 的)的 上、下、左、右 位置写一个注释
    note [top | bottom | left | right] of ClassName
    这里是笔记部分
    end note
    
    # 用法二:写好一个笔记,命名为 noteName(不能重复)
    note as noteName
    这里是笔记部分
    end note
    
    # 然后可以将笔记关联到类 ClassName(也可以不指定)
    noteName .. ClassName
    
    # 用法三:紧跟在类/接口声明之后
    class A note left: "这里是笔记部分"
    
    # 用法四:属性/方法注释,通过 类名::[属性名|方法名] 来指向类的属性或方法,具体用法同用法一和用法二,当方法名相同时,需要把参数列表也加上
    
    note right of A::counter
    This member is annotated
    end note
    
    note right of A::"start(int timeoutms)"
    This method with int
    end note
    
    note right of A::"start(Duration timeout)"
    This method with Duration
    end note
    
    
  • "" 用在关联线上,表示一些含义,紧跟在类之后,例如多重性关联中用于说明类之间的连接个数;

    # 例如一个页面中有零个多或个按钮,但是一个按钮只属于一个页面
    From "1..1" --> "0..*" Button : "关联线中间说明"
    
  • PlantUML 默认自动将方法和属性重新分组,可以使用分隔符 .. | == | -- | __ 来对方法或者属性进行重排;

    class User {
    .. Simple Getter ..
    + getName()
    + getAddress()
    .. Some setter ..
    + setName()
    __ private data __
    int age
    -- encrypted --
    String password
    }
    

Java 设计模式_第1张图片

@startuml
'https://plantuml.com/class-diagram

interface D
A --- B: "双向关联"
A --> B: "单向关联"
A "1..1" --> "0..*" B: "多重性关联"
A o-- B: "A 聚合 B"
A *-- B: "A 组合 B"
A ..> B: "A 依赖 B"
A --|> B: "A 继承 B"
C -> C: "自关联"
C ..|> D: "C 实现 D"

note bottom of D
笔记
可换行
可用部分HTML
<u>下划线</u>
<size:15>size字体大小</size>
<color:green>颜色</color>
end note

@enduml
1. 关联关系
  1. 双向关联:A -- B
  2. 单向关联:A --> B
  3. 自关联:C --> C
  4. 多重性关联:A "1..1" --> "0..*" B
  5. 聚合关系(contains):A 聚合 B:A o-- B
  6. 组合关系(has):A 组合 B:A *-- B
2. 依赖关系

A 依赖 B:A ..> B

3. 泛化关系(继承)

A 继承 B:A --|> B

4. 接口与实现关系

C 实现 D:C ..|> D

二、设计原则

设计模式包含了面向对象的精髓:“懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要”

编写软件过程中,程序员面临着来自 耦合性、内聚性以及可维护性、可拓展性、重用性、灵活性等多方面的挑战,设计模式是为了让程序(软件)具有更好的:

  1. 代码重用性(不重复造轮子)
  2. 可读性(编程规范性,便于阅读和理解)
  3. 可拓展性(增加新功能时,很方便)
  4. 可靠性(增加新功能后,对原来的功能没有影响)
  5. 使程序呈现高内聚、低耦合的特性

1. 单一职责原则

1.1. 定义

单一职责原则 def:

  1. 一个对象应该只包含单一的职责,并且该职责该完整地封装在一个类中;
  2. 易理解版: 就一个类而言,应该仅有一个引起它变化的原因;
  1. 单一职责原则是实现 高内聚、低耦合 的指导方针;
  2. 类的职责有两方面:
    1. 数据职责:通过属性来体现
    2. 行为职责:通过方法来体现
  3. 类的职责越多,被复用的可能性越低;
1.2. 总结

单一职责原则可以:

  1. 降低类的复杂度,一个类只负责一项职责;
  2. 提高类的可读性,可维护性
  3. 降低变更引起的风险
  4. 通常情况下,应当遵守单一职责原则;
    • 只有逻辑足够简单,才可以在代码级违反单一职责原则;
    • 只有类中方法数量足够少,可以在方法级别保持单一职责原则;

2. 接口隔离原则

2.1. 定义

接口隔离原则 def: 客户端不应该依赖那些它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上;

注意:该定义中的接口指的是所定义的方法;

2.2. 类图

有如下类图:

Java 设计模式_第2张图片

通过接口隔离原则将上述类图进行重构:

  1. 类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,如果接口 Interface1 对于 类 A 和类 C磊说不是最小接口,那么类 B 和类 D 必须去实现它们不需要的方法
  2. 将接口 Interface1 拆分为独立的几个接口,类 A 和类 C 分别与它们需要的接口建立依赖关系;(采用接口隔离原则)
  3. 根据实际情况将 Interface1 中出现的方法差分为三个接口

Java 设计模式_第3张图片

3. 依赖倒转原则

3.1. 定义

依赖倒转原则 def:

  1. 高层模块不应该依赖低层模块,他们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象;
  2. 易理解版: 要针对接口编程,不要针对实现编程;
  1. 依赖倒转(倒置)原则的中心思想是 面向接口编程
  2. 相对于细节(具体实现类)的多变性,抽象(接口或抽象类)的东西要稳定得多;
  3. 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成;
3.2. 分析
  1. 类之间的耦合,类之间有三种耦合关系(依赖关系)
    1. 零耦合关系,两个类之间没有任何的耦合关系,称为零耦合;
    2. 具体耦合关系,具体耦合发生在两个具体类(可实例化的类)之间,由一个类对另一个具体类实例的直接引用产生;
    3. 抽象耦合关系,抽象耦合关系发生在一个具体类和一个抽象类之间,也可以发生在两个抽象类之间,使两个发生关系的类之间存有最大的灵活性;由于在抽象耦合中至少有一端是抽象的,因此可以通过不同的具体实现来进行拓展;
  2. 依赖注入(Dependence Injection,DI),是如何传递对象之间的依赖关系;
    1. 构造注入(Construtor Injection):构造函数注入实例变量;
    2. 设值注入(Setter Injection):Setter 方法注入实例变量;
    3. 接口注入(Interface Injection):通过接口方法注入实例变量;
3.3. 总结
  1. 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好;
  2. 变量的声明类型都是抽象类或接口,利于程序拓展和优化
  3. 继承时遵循里氏替换原则

4. 里氏替换原则

4.1. OO 中的继承性
  1. 父类中已经实现好的方法,若子类对这些方法任意修改,就会对整个继承体系造成破坏
  2. 继承是把双刃剑,带来便利的同时会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性;父类修改后必须考虑所有的子类;
  3. 正确的使用继承关系:=> 里氏替换原则
4.2. 定义

里氏代换原则 def:

  1. 如果对每一个类型为 S 的对象 O1,都有类型为 T 的对象 O2,使得以 T 类型定义的所有程序 P 在所有对象 O1 都替换 O2 时,程序 P 的行为没有变化,那么类型 S 是类型 T 的子类型;
  2. 易理解版: 所有引用基类(父类)的地方必须能透明的使用其子类的对象;
  1. 里氏替换原则是实现开闭原则的重要方式之一;
  2. 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类类型对象来替换父类对象;
  3. 使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
  4. 继承让两个类耦合性增强了,在适当情况下,可以通过聚合、组合、依赖关系来解决问题;
  5. 改进方案:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合、组合等关系替代;
4.3. 类图

Java 设计模式_第4张图片

5. 开闭原则

5.1. 定义

开闭原则 def: 一个软件实体应当对扩展开放,对修改关闭;

  1. 开闭原则是编程中最基础、最重要的设计原则;
  2. 一个软件实体类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现拓展细节;
  3. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化;
  4. 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则;
5.2. 类图

Java 设计模式_第5张图片

6. 迪米特法则

6.1. 定义

迪米特法则 def:

  1. 一个软件实体应当尽可能少的与其他实体发生相互作用;
  2. 易理解版: 只与直接的朋友通信;其中,直接的朋友指:
    1. 当前对象本身(this)
    2. 以参数形式传入到当前对象方法中的对象
    3. 当前对象的成员对象
    4. 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友
    5. 当前对象所创建的对象(返回值)
  1. 一个对象应该对其他对象保持最少的了解;
  2. 类与类的关系越密切,耦合度越大;
  3. 迪米特法则(Demeter Principle) 又叫 最少知道原则,即一个类对自己依赖的类知道的越少越好;对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部,对外除了提供的 public 方法,不对外暴露任何信息;
6.2. 总结
  1. 迪米特法则的核心是 降低系统的的耦合度,使类与类之间保持松散的耦合关系;

    每个类都减少了不必要的依赖,迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系;

  2. 迪米特法则的主要用途在于控制信息的过载;

  3. 在将迪米特法则运用到系统设计中时,需要注意:

    1. 在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大的波及;
    2. 在类的结构设计上,每一个类都应当尽量 降低其成员变量和成员函数的访问权限
    3. 在类的设计上,只要有可能,一个类型应当设计成 不变类
    4. 在对其他类的引用上,一个对象 对其他对象的引用应当降到最低

7. 合成复用原则

7.1. 定义

合成复用原则 def: 尽量使用对象组合(组合关系/聚合关系),而不是继承来达到复用的目的;又称为 组合/聚合服用原则

通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;通过委派调用已有对象的方法达到复用其已有功能的目的;要尽量使用组合/聚合关系,少用继承;

7.2. 类图

Java 设计模式_第6张图片

三、创建型模式

1. 单例模式

1.1. 定义

单例模式(Singleton Pattern) def: 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类;

  1. 某个类只能由一个实例
  2. 必须自行创建这个实例
  3. 必须向整个系统提供这个实例

使用场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(如:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)

1.2. java 中实现单例模式

实际上有 7 中实现单例模式的方法:饿汉式(静态常量)、饿汉式(静态代码块)、懒汉式(非线程安全)、懒汉式(同步方法)、双重检查(懒汉式)、

1.2.1. ⭐饿汉式(静态常量)
// 饿汉式(静态常量)
class Singleton {
    // 1.构造器私有化,外部不能 new
    private Singleton() {}
    // 2. 本类内部创建对象实例
    private final static Singleton instance = new Singleton();
    // 3.提供一个静态的公有方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}
  • 优点:写法简单,在类装载的时候就完成了实例化(JVM 部分内容);
  • 缺点:没有达到懒加载(Lazy Loading)效果,如果从始至终未使用这个实例,会造成内存的浪费;
  • 结论:这种单例模式可用,但是可能造成内存浪费;
1.2.2. ⭐饿汉式(静态代码块)
// 饿汉式(静态代码块)
class Singleton {
    private Singleton() {}
    private static Singleton instance = null;
    static { // 在静态代码块中创建单例对象
        instance = new Singleton();
    }
    public static Singleton getInstance() {
        return instance;
    }
}
  • 优点:写法简单,在类装载的时候就完成了实例化(JVM 部分内容);
  • 缺点:没有达到懒加载(Lazy Loading)效果,如果从始至终未使用这个实例,会造成内存的浪费;
  • 结论:这种单例模式可用,但是可能造成内存浪费;
1.2.3. 懒汉式(非线程安全)
// 懒汉式(非线程安全)
class Singleton {
  	private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) { // 将对象的创建延迟到调用时
            instance = new Singleton();
        }
        return instance;
    }
}
  • 优点:达到了懒加载(Lazy Loading)效果;
  • 缺点:存在线程安全问题;
  • 结论:在实际开发中,不要使用这种方式;
1.2.4. 懒汉式(线程安全,同步方法)
// 懒汉式(线程安全,同步方法)
class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    // 提供一个静态的公有方法,加入同步处理代码,解决线程安全问题
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 优点:达到了懒加载(Lazy Loading)效果,解决线程安全问题;
  • 缺点:效率太低,每个线程在想要获得类的实例的时候,执行 getInstance() 方法都要进行同步,实际上只要进行一次实例化代码就够了;
  • 结论:在实际开发中,不推荐使用这种方式;
1.2.5. 懒汉式(线程安全,同步代码块)
// 懒汉式(线程安全,同步代码块)
class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    // 提供一个静态的公有方法,加入同步处理代码,解决线程安全问题
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}
  • 这种方式本意上是想对同步方法的实现方式进行改进,但是前面同步方法的效率太低,改为同步产生实例化的代码块;
  • 但是这种同步并不能起到线程同步的作用!!!
  • 结论:在实际开发中,不能使用这种方式;
1.2.6. ⭐双重检查

双重检查 Double-Check 概念是多线程开发中经常使用到的;

双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行

// 双重检查
class Singleton {
    // volatile 禁止指令重排(JVM 做的优化措施)
    private static volatile Singleton instance = null;
    private Singleton() {}
    // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

在 Java 中,加入 volatile 关键字是很有必要的!

Java 平台的内存模型允许“无序写入”,

因为 instance = new Singleton() 该语句并非原子操作,实际上分了三个步骤:

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将给 Singleton 对象指向分配的内存空间(此时singleton才不为null)

由于虚拟机的指令重排序:执行顺序可能是 1、3、2,分配内存并修改指针后未初始化;

第一个线程初始化对象到一半,第二个线程来发现已经不是 null 了就直接返回了 实际上该对象此时还没有完全初始化 可能会出现这个问题

  • 达到了懒加载(Lazy Loading)效果,解决线程安全问题,效率也较高
  • 结论:在实际开发中,推荐使用这种方式;
1.2.7. ⭐静态内部类
// 静态内部类
class Singleton {
    private Singleton() {}
    // 静态内部类,有一个静态属性 Singleton
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

静态内部类在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化;

  • 优点:采用类加载机制保证线程安全问题,利用静态内部类特点实现延迟加载,效率高;
  • 结论:推荐使用
1.2.8. ⭐枚举
// 使用枚举,可以实现单例
enum Singleton {
    INSTANCE;
    public void method() {
        // 方法
        System.out.println("Fucking");
    }
}
  • 优点:无线程安全问题,还能防止反序列化重新创建新的对象
  • 结论:推荐使用;

2. 简单工厂模式

2.1. 定义

简单工厂模式 def: 又称为静态工厂方法模式,可以根据参数的不同返回不同类的实例;

简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类;其核心是工厂类;

2.2. 模式结构

Java 设计模式_第7张图片

2.3. 实例说明

下面类图是一个披萨订购的类图,当需要新增一种口味的披萨时,需要修改较多的代码,且违反了开闭原则(对扩展(提供方)开放,对修改(使用方)关闭)

Java 设计模式_第8张图片

使用简单工厂模式对上面的结构改进:

把创建 Pizza 对象封装到一个类中,当有新的 Pizza 种类时,只需要修改该类即可,其他原有的代码都不需要改动,改进后的类图如下:

Java 设计模式_第9张图片

3. 工厂方法模式

3.1. 定义

工厂方法模式(Factory Method Factory) def: 又称工厂模式,也叫虚拟构造器模式或多态工厂模式;工厂父类负责定义创建产品对象的公共接口,而工厂子类负责生成具体的产品对象;

目的是 :将产品类的实例化操作延迟到工厂子类中完成,即通过子类工厂来确定究竟应该实例化哪一个具体产品类;

3.2. 模式结构

Java 设计模式_第10张图片

3.3. 实例说明

在简单工厂模式中的实例披萨项目中增加新的需求:客户在点披萨时,可以选择不同口味的披萨,比如:北京的奶酪pizza、北京的胡椒pizza或者是伦敦的奶酪pizza、伦敦的呼叫pizza;

Java 设计模式_第11张图片

4. 抽象工厂模式

4.1. 定义

抽象工厂模式 def: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类,抽象工厂模式又称为 Kit 模式

抽象工厂模式是工厂方法模式的泛化版,在工厂方法模式中,每一个具体工厂只能生产一种具体产品类,而在抽象工厂方法模式中,每一个具体工厂可以生产多个具体产品类;

工厂方法模针对的是同一个产品等级结构(如都是 Pizza),而抽象工厂模式则需要面对多个产品等级结构(如海尔工厂:海尔电冰箱,海尔电视,海尔空调);

4.2. 模式结构

Java 设计模式_第12张图片

4.3. 实例说明

Java 设计模式_第13张图片

5. 原型模式

5.1. 定义

原型模式(Prototype Pattern) def: 用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象;

包含如下角色:

  1. Prototype(抽象原型类):声明一个克隆自己的方法,是所有具体原型类的公共父类,可以是抽象类或接口;
  2. ConcretePrototype(具体原型类):实现具体的克隆方法,在克隆方法冲返回自己的一个克隆对象;
  3. Client(客户类)
5.2. 模式结构

Java 设计模式_第14张图片

5.3. 深拷贝与浅拷贝

Java 中通过覆盖 Object 类的 clone() 方法可以实现 浅克隆(浅拷贝);

浅拷贝仅复制基本数据类型,深拷贝除了复制基本数据类型外,还复制了引用数据类型;

Java 设计模式_第15张图片

  1. 浅拷贝:

    • 对于基本数据类型,直接进行值传递(即将该属性的值复制一份给新的对象);
    • 对于引用数据类型,会进行引用传递(即将引用值复制一份给新的对象,两个引用指向同一个实例);
    • 浅拷贝是使用默认的 clone() 方法来实现的;
  2. 深拷贝

    • 对于基本数据类型,直接进行值传递(即将该属性的值复制一份给新的对象);

    • 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达所有的对象;

    • 深拷贝实现方式:

      序列化(Setialization)是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中;

      1. 重写 clone() 方法,递归实现;
      2. ⭐通过对象序列化(需实现 Serializable 接口)实现(将对象写到一个流中,再从流中读取出来,就可以实现深克隆)
public class Sheep implements Cloneable, Serializable {
  private String name;
  private List list;
  // 使用默认的克隆方法实现浅拷贝
  protected Object clone() throws CloneNotSupportedException {
    Sheep sheep = null;
    try {
      sheep = (Sheep) super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return sheep;
  }
  // 使用序列化的方式实现深拷贝
  protected Object deepClone() {
    ByteArrayOutputStream bos = null;
    ObjectOutputStream oos = null;
    ByteArrayInputStream bis = null;
    ObjectInputStream ois = null;
    Object object = null;
    try {
      // 序列化
      bos = new ByteArrayOutputStream();
      oos = new ObjectOutputStream(bos);
      oos.writeObject(this);
      // 反序列化
      bis = new ByteArrayInputStream(bos.toByteArray());
      ois = new ObjectInputStream(bis);
      object = ois.readObject();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return object;
  }
}
public class Client { // 测试类
  public static void main(String[] args) throws CloneNotSupportedException {
    Sheep sheep = new Sheep("tom");
    List<String> list = new ArrayList<>();
    list.add("123");
    list.add("456");
    sheep.setList(list);
    System.out.println("--------------- 使用默认的 clone 方法 ---------------");
    Sheep sheep1 = (Sheep) sheep.clone();
    System.out.println(sheep);
    System.out.println(sheep1);
    list.add("Test: 浅拷贝"); // 若是浅拷贝,则 sheep1 的 list 与 sheep 相同
    System.out.println("----------- 更改引用数据类型后 -----------");
    System.out.println("sheep: " + sheep);
    System.out.println("sheep1: " + sheep1);
    System.out.println("--------------- 序列化实现深拷贝 ---------------");
    Sheep sheep2 = (Sheep) sheep.deepClone();
    System.out.println("sheep: " + sheep);
    System.out.println("sheep2: " + sheep2);
    list.add("Test: 深拷贝 or 浅拷贝"); // 若是深拷贝,则 sheep2 的 list 与 sheep 不同
    System.out.println("----------- 更改引用数据类型后 -----------");
    System.out.println("sheep: " + sheep);
    System.out.println("sheep2: " + sheep2);
  }
}

Java 设计模式_第16张图片

6. 建造者模式

6.1. 定义

建造者模式(Builder Pattern)def: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。又称为 生成器模式

包含如下角色:

  1. Builder(抽象建造者):抽象建造者为创建一个产品 Product 对象的各个部件指定抽象接口,一类方法是 buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是 getResult(),它们用于返回复杂对象;抽象建造者既可以是抽象类,也可以是接口;
  2. ConcreteBuilder(具体建造者):具体实现 Builder 接口/抽象方法;
  3. Product(产品角色):被构建的复杂对象,包含多个组成部件;
  4. Director(指挥者):又称为导演类,负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其 construct() 建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的构造;客户端一般只需要与指挥者进行交互;
6.2. 模式结构

Java 设计模式_第17张图片

6.3. 实例说明

利用建造者模式描述 KFC 如何创建套餐:套餐是一个复杂对象,它一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、可乐等)等组成部分,不同的套餐有不同的组成部分,而 KFC 的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐,然后返回给顾客;

Java 设计模式_第18张图片

四、结构型模式

1. 适配器模式

1.1. 定义

适配器模式(Adapter Pattern)def: 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper),因而适配器模式也称 包装器模式

适配器模式既可以作为 类结构型模式,也可以作为 对象结构型模式

包含如下角色:

  1. Traget(目标抽象类): 定义客户要用的特定领域的接口,可以是抽象类或接口,也可以是具体类;在类适配器中,由于 Java 不支持多继承,它只能是接口;
  2. Adapter(适配器类): 适配器类可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配;
  3. Adaptee(适配者类): 适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配;
  4. Client(客户类)
1.2. 模式结构

Java 设计模式_第19张图片

Java 设计模式_第20张图片

1.3. 实例说明
1.3.1. 电源适配器(类适配器模式)

生活实例,家用电插座上的电压都是 220V 直流电,手机等电子设备充电所需电压一般都小于 220V,如手机充电器大部分都是 5V 直流电;需要通过电源适配器使用;

Java 设计模式_第21张图片

1.3.2. 电源适配器(对象适配器模式)

根据《合成复用原则》,使用关联关系类代替继承关系;

Java 设计模式_第22张图片

1.4. 模式优缺点
  1. 类适配器模式、对象适配器模式的共同优点:
    1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无需求改原有的代码;
    2. 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性;
    3. 符合开闭原则;
  2. 对象适配器的优点: 可以把多个不同的适配者适配到同一个目标,即可以弥补下面类适配器的缺点;
  3. 类适配器模式的缺点: 对于Java、C# 等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为接口,不能讲一个适配者类和它的子类都适配刀目标接口,有一定局限性;
  4. 对象适配器的缺点: 与类适配器相比,很难置换适配者类的方法;
1.5. 缺省(接口)适配器模式

缺省适配器模式(Default Adapter Pattern)def: 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现该接口,并未接口中的每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求;

Java 设计模式_第23张图片

2. 桥接模式

2.1. 定义

桥接模式(Bridge Pattern)def: 将抽象部分与它的实现部分分离,使它们都可以独立地变化;又称为 柄体(Handle and Body)模式接口(Interface)模式; 桥接模式是一种 对象结构型模式;

桥接模式的重点是将抽象化与实现脱藕;

Bridge 模式基于 类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责;

有如下角色:

  1. Abstraction(抽象类): 定义抽象类的接口,一般是抽象类,其中定义了一个 Implementor(实现抽象类)类型的对象并可以维护该对象,它与 Implementor 具有关联关系;
  2. RefinedAbstraction(扩充抽象类): 扩充由 Abstraction 定义的接口,一般是具体类;
  3. Implementor(实现类接口): 定义实现类的接口;
  4. ConcreteImplementor(具体实现类): 实现 Implementor 接口并且具体实现它;
2.2. 模式结构

image-20220517211459482

2.3. 实例说明

Java 设计模式_第24张图片

2.4. 模式优缺点
  1. 优点:
    1. 分离抽象接口及其实现部分
    2. 桥接模式 优于多继承(类爆炸、复用性差、违反单一职责原则)
    3. 提高了系统的 可扩展性,在两个维度中任意扩展一个维度,都不需要修改原有的系统
    4. 实现细节对客户透明,可以对用户隐藏实现细节
  2. 缺点:
    1. 引入桥接模式会 增加系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者 针对抽象进行设计与编程
    2. 桥接模式要求正确识别出系统中两个独立变化的维度,因此 使用范围具有一定局限性

3. 装饰模式

3.1. 定义

装饰模式(Decorator Pattern)def: 动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更加灵活;装饰模式是一种 对象结构型模式;

通过关联关系,将一个类的对象 objA 嵌入到另一个新对象 objB 中,由 objB 来决定是否调用嵌入对象 objA 的行为并扩展新的行为,称 objB 为装饰器(Decorator);

有如下角色:

  1. Component(抽象构件): 定义了对象的接口,可以给这些对象动态增加职责(方法);
  2. ConcreteComponent(具体构件): 定义具体的构建对象,实现了在抽象构建中声明的方法,装饰器可以给他增加额外的职责;
  3. Decorator(抽象装饰类): 是抽象构件类的子类,用于给具体构件增加职责;具体职责在其子类中实现,维护一个指向抽象构件对象的引用;
  4. ConcreteDecorator(具体装饰类): 向构件添加新的职责;
3.2. 模式结构

Java 设计模式_第25张图片

3.3. 实例说明

Java 设计模式_第26张图片

3.4. IO

Java 设计模式_第27张图片

3.5. 模式优缺点
  1. 优点:
    1. 较继承关系灵活性更高
    2. 动态拓展一个对象的功能(继承是静态的)
    3. 有许多种排列组合
    4. 符合开闭原则
  2. 缺点:
    1. 会产生很多小对象
    2. 容易出错,多次装饰的对象需要逐级排错,较为困难

4. 组合模式

4.1. 定义

组合模式(Composite Pattern): 组合多个对象形成树形结构以表示 “部分—整体” 的结构层次;又称 “部分—整体” 模式;组合模式是一种 对象结构型模式;

有如下角色:

  1. Component(抽象构件): 为叶子构件和容器构件对象生命接口,可以是接口或抽象类;
  2. Leaf(叶子构件): 表示节点对象,叶子节点没有子节点;
  3. Composite(容器构件): 表示容器节点对象,包含子节点,子节点可以是叶子节点,也可以是容器节点,提供一个集合用于存储子节点;
  4. Client
4.2. 模式结构

Java 设计模式_第28张图片

4.3. 模式优缺点
  1. 优点:
    1. 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构建更加容易;
    2. 可以无限组合,形成复杂的树形结构
    3. 在组合体内加入对象构件,不必更改原有代码
  2. 缺点:
    1. 使设计变得更加抽象
    2. 增加新构件时很难对容器种的构建类型进行

5. 外观模式

5.1. 定义

外观模式(Facade Pattern)def: 为子系统中的一组接口提供一个统一的入口,外观模式又称为 门面模式; 外观模式是一种 对象结构型模式;

  • 通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只跟这个接口发生调用;
  • 外观模式的目在于降低系统的复杂程度
  • 外观模式不给系统增加任何新功能,它仅仅是增加一些简单化的接口;
  • 引入外观模式后,增加新的子系统或者移除子系统都非常方便,客户端无需进行修改(或者极少的修改),只需要在外观类中增加或移除对子系统的引用即可,从这一点来看,外观模式在一定程度上并不符合开闭原则;

有如下角色:

  1. Facade(外观角色): 将所有从客户端发来的请求委派到相应的子系统中去;
  2. SubSystem(子系统角色): 可以同时有一个或多个子系统角色,每个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;
5.2. 模式结构

Java 设计模式_第29张图片

Java 设计模式_第30张图片

5.3. 模式优缺点
  1. 优点:
    1. 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易
    2. 实现了子系统与客户之间的松耦合关系
  2. 缺点:
    1. 不能很好地限制客户使用子系统类
    2. 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源码,违背了开闭原则

6. 享元模式

6.1. 定义

享元模式(Flyweight Pattern) def: 运用共享技术有效地支持大量细粒度对象的复用;享元模式是 对象结构型模式

  • 享元模式结构较为复杂,一般结合工厂模式一起使用,它的结构图中包含了一个享元工厂类;
  • 有以下角色:
    1. Flyweight(抽象享元类): 声明一个接口,通过它可以接受并作用于外部状态
    2. ConcreteFlyweight(具体享元类): 其实例称为享元对象,为内部状态提供了存储空间,由于具体享元对象必须是可以共享的,因此它所存储的状态必须是内部的,即他独立存在于自己的环境中;
    3. UnsharedConcreteFlyweight(非共享具体享元类): 不能被共享的具体享元类
    4. FlyweightFactory(享元工厂类): 用于创建并管理享元对象;
  • 享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统性能;
  • 享元模式以共享的方式高效地支持大量地细粒度对象,享元模式能够做到共享的关键是 区分内部状态(internal state)和外部状态(external state)
    1. 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享;如:字符的内容不会随环境的变化而变化;
    2. 外部状态是随环境改变而改变的、不可以共享的状态;享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候传入到享元对象内部;
6.2. 模式结构

Java 设计模式_第31张图片

6.3. 模式优缺点
  1. 优点:
    1. 极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份
    2. 外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同环境中被共享
  2. 缺点:
    1. 享元模式使得系统更加复杂,需要分理处内部状态和外部状态,使得程序的逻辑复杂化
    2. 读取外部状态需要消耗时间,使得运行时间变长

7. 代理模式

7.1. 定义

代理模式(Proxy Pattern)def: 给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做 Proxy 或 Surrogate,它是一种 对象结构型模式;

代理模式有主要三种不同形式:静态代理、动态代理(JDK 代理、接口代理)和 Cglib 代理(可以在内存中动态的创建对象,而不需要实现接口)

有如下角色:

  1. Subject(抽象主题角色): 声明了真实主题和代理主题的共同接口;
  2. Proxy(代理主题角色): 内部包含对真实主题的引用,从而可以在任何时候操作真实主题对象;
  3. RealSubject(真实主题角色): 真实主题角色定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理角色间接调用真实主题角色中定义的方法
7.2. 模式结构

Java 设计模式_第32张图片

7.3. 模式优缺点
  1. 优点:
    1. 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
    2. 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求
    3. 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度
    4. 保护代理可以控制对真实对象的使用权限
  2. 缺点:
    1. 请求速度可能会变慢
    2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂

五、行为型模式

1. 模板方法模式

1.1. 定义

模板方法模式(Template Method Pattern)def: 定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法模式使得子类可以不改变一个算法结构即可重定义该算法的某些步骤;模板方法模式是一种 类行为型模式

有如下角色:

  1. AbstractClas(抽象类): 定义一系列的基本操作,可以是具体的,也可以是抽象的;
  2. ConcreteClass(具体子类): 实现了抽象类的基本操作以及特定算法的步骤;
1.2. 模式结构

Java 设计模式_第33张图片

  • 一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的过程;

  • 基本方法是实现算法各个步骤的方法,是模板方法的组成部分:

    1. 抽象方法: 由抽象类声明、具体子类实现

    2. 具体方法: 由抽象类或子类声明并实现,其子类可以进行覆盖,也可以直接继承

    3. 钩子方法: 由一个抽象类或具体类声明并实现,其子类可能会加以拓展。通常在父类中给出的实现是一个空实现,并以该空实现作为方法的默认实现;在模板方法模式中,钩子方法有两类:

      1. 与一些具体步骤 “挂钩”,通常返回值是 boolean 类型的,方法名一般为 isXXX(),用于对某个条件进行判断。一般情况下钩子方法返回值都是 true,如果不希望某方法执行,可以在子类中覆盖钩子方法,将返回值置为 false 即可;

        public void template() {
          open();
          display();
          if (isPrint()) {
            print();
          }
        }
        public isPrint() {
          return true;
        }
        
      2. 实现体为空的具体方法,子类可以根据需要覆盖或继承这些钩子方法;

  • 可以将模板方法声明为 final,防止子类覆盖核心方法;

1.3. 模式优缺点
  1. 优点:
    1. 子类定义详细的处理算法时不会改变算法的结构
    2. 模板方法模式是一种代码复用的基本技术,在类库设计中尤为重要
    3. 提供了一种反向的控制结构,通过父类调用其子类的操作,通过对子类的拓展增加新的行为,符合开闭原则
  2. 缺点:
    1. 每个不同的实现都要定义一个子类,会导致类的个数增加,系统更加庞大,设计也更加抽象,但是更符合 “单一职责原则” ,使得类的内聚性得以提高

2. 命令模式

2.1. 定义

命令模式(Command Pattern)def: 将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作;别名为动作(Action)模式或事务(Transaction)模式;命令模式是一种 对象行为型模式;

  • 命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求;
  • 请求的一方发出请求,要求执行一个操作;接受的一方收到请求,并执行操作;

有如下角色:

  1. Command(抽象命令类): 一一般是一个接口,声明了用于执行请求的 execute() 等方法,通过这些方法可以调用请求接收者的相关操作;
  2. ConcreteCommand(具体命令类): 实现了抽象命令类中声明的方法,对应具体的接受者对象,绑定接收者对象的动作;
  3. Invoker(调用者): 请求的发送者,通过命令对象来执行请求;
  4. Receiver(接收者): 执行与请求相关的操作,具体实现对请求的业务处理
  5. Client
2.2. 模式结构

Java 设计模式_第34张图片

2.3. 模式优缺点
  1. 优点:
    1. 降低系统的耦合度
    2. 新的命令可以很容易地加入到系统中
    3. 比较容易地设计一个命令队列和宏命令(组合命令)
    4. 可以方便地实现对请求地 Undo 和 Redo
  2. 缺点:
    1. 针对每个命令都要设计一个具体命令类,会导致系统中有过多的具体命令类

3. 访问者模式

3.1. 定义

访问者模式(Visitor Pattern)def: 表示一个作用于某对象结构中各个元素地操作,它使我们可以在不改变个元素地类地前提下定义作用于这些元素地新操作;访问者模式是一种 对象行为型模式;

有如下角色:

  1. Visitor(抽象访问者): 为对象结构类中每一个具体元素类 COncreteElement 声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作
  2. ConcreteVisitor(具体访问者): 实现抽象访问者声明的操作
  3. Element(抽象元素): 一般是抽象类或接口,定义一个 accept() 方法,该方法以一个抽象访问者作为参数
  4. ConcreteElement(具体元素): 实现了 accept() 方法,在其中调用访问者的访问方法以便完成对一个元素的操作
  5. ObjectStructure(对象结构): 对象结构是一个元素的集合,用于存放元素对象,并且提供了遍历其内部元素的方法,可以结合组合模式来实现,也可以是一个简单的集合对象;
3.2. 模式结构

Java 设计模式_第35张图片

3.3. 实例说明

Java 设计模式_第36张图片

3.4. 模式优缺点
  1. 优点:
    1. 使得增加新的访问操作变得很容易
    2. 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散到一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用;
    3. 可以跨过类的等级结构访问属于不同的等级结构的元素类
    4. 让用户能够在不修改现有类层次结构的情况下,定义该类层次结构的新操作
  2. 缺点:
    1. 增加新的元素类很困难
    2. 破坏封装

4. 迭代器模式

4.1. 定义

迭代器模式(Iterator Pattern)def: 提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor);迭代器模式是一种 对象行为型模式;

有如下角色:

  1. Iterator(抽象迭代器): 定义了访问和遍历元素的接口,一般声明如下方法:用于获取第一个元素的 first(),用于访问下一个元素的 next(),用于判断是否还有下一个元素的 hasNext(),用于获取当前元素的 currentItem()
  2. ConcreteIterator(具体迭代器): 实现了抽象迭代器接口,完成对聚合对象的遍历,同时在对聚合进行遍历时跟踪其当当前位置;
  3. Aggregate(抽象聚合类): 用于存储对象,并定义创建相应迭代器对象的接口,声明一个 createIterator() 方法用于创建一个迭代器对象;
  4. ConcreteAggregate(具体聚合类): 实现了创建相应迭代器的接口,实现了 createItreator() 方法并返回一个与该具体聚合对相应的具体迭代器 ConcreteIterator 实例;
4.2. 模式结构

Java 设计模式_第37张图片

聚合对象职责:

  1. 存储内部数据
  2. 遍历内部数据
4.3. 实例说明
4.3.1. MyCollection
public interface MyCollection<T> {
  MyIterator<T> createIterator();
}
4.3.2. MyIterator
public interface MyIterator<T> {
  void first();
  void next();
  boolean isLast();
  T currentItem();
}
4.3.3. NewCollection
public class NewCollection<T> implements MyCollection<T> {
  private T[] obj;
  public NewCollection(T[] obj) {
    this.obj = obj;
  }
  public MyIterator<T> createIterator() {
    return new NewIterator<T>();
  }
  private class NewIterator<T> implements MyIterator<T> {
    private int currentIndex = 0;
    public void first() {
      currentIndex = 0;
    }
    public void next() {
      if (currentIndex < obj.length) {
        currentIndex++;
      }
    }
    public boolean isLast() {
      return currentIndex >= obj.length;
    }
    public T currentItem() {
      return (T) obj[currentIndex];
    }
  }
}
4.3.4. MyObject
public class MyObject {
  private String name;
  public MyObject(String name) {
    this.name = name;
  }
  @Override
  public String toString() {
    return "MyObject{" +
      "name='" + name + '\'' +
      '}';
  }
}
4.3.5. Client
public class Client {
  public static void process(MyCollection<MyObject> collection) {
    MyIterator<MyObject> i = collection.createIterator();
    while (!i.isLast()) {
      System.out.println(i.currentItem().toString());
      i.next();
    }
  }
  public static void main(String[] args) {
    MyObject[] objects = new MyObject[10];
    for (int i = 0; i < 10; i++) {
      objects[i] = new MyObject("我是甜狗 " + i + " 号");
    }
    MyCollection<MyObject> collection = new NewCollection<>(objects);
    process(collection);
  }
}
4.4. 模式优缺点
  1. 优点:
    1. 支持不同的方式遍历一个聚合对象
    2. 简化了聚合类
    3. 在一个聚合上可以有多个遍历
    4. 增加新的聚合类和迭代器类都很方便,无需修改原有代码,符合开闭原则
  2. 缺点:
    1. 将存储数据和遍历数据职责分离,增加新的聚合类时要对应增加新的迭代器类,类的个数成对增加,一定程度上增加了系统的复杂性

5. 观察者模式

5.1. 定义

观察者模式(Pattern)def: 定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并自动更新;又称 发布—订阅(Publish/Subscribe)模式、模型—视图(Model/View)模式、源—监听(Source/Listener)模式或从属者(Dependents)模式; 观察者模式是一种 对象行为型模式

  • 观察者模式在 Java 语言中十分重要,在 JDK 中(java.util 包下)提供了 Observable 类以及 Observer 接口,构成了 Java 语言对观察者模式的支持;

  • 有如下角色:

    1. Subject(目标): 又称为主题,指被观察的对象;定义了一个观察者集合;目标可以是接口、抽象类或实现类;
    2. ConcreteSubject(具体目标): 目标的子类,通常包含经常发生改变的数据,当状态发生改变时,向它的各个观察者发出通知;
    3. Observer(观察者): 对观察目标的改变作出反应,一般定义为接口,声明了更新数据的方法 update()
    4. ConcreteObserver(具体观察者): 维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标的状态保持一致,实现了父类的 update() 方法;
5.2. 模式结构

Java 设计模式_第38张图片

5.3. 实例说明

利用观察者模式实现:天气更新后各个网站自动更新数据并显示;

Java 设计模式_第39张图片

MVC 模式就是利用观察者模式实现的 :

Java 设计模式_第40张图片

5.4. 模式优缺点
  1. 优点:
    1. 观察者模式可以实现表示层和数据逻辑层分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色;
    2. 观察者模式观察目标和观察者之间建立一个抽象的耦合;
    3. 观察者模式支持广播通信,观察目标会向所有注册的观察者发出通知,简化了一对多系统的设计难度;
    4. 符合开闭原则;
  2. 缺点:
    1. 观察者和目标之间如果有循环依赖,会导致循环调用,可能导致系统崩溃;
    2. 如果一个观察目标有很多直接或间接的观察者,将所有的观察者都通知到会花费很多时间;
    3. 观察者只能知道观察目标发生变化,但不知道是怎么变化的;

6. 中介者模式

6.1. 定义

中介者模式(Mediator Pattern)def: 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互;中介者模式是一种 对象行为型模式

  • 中介者有以下两种职责:
    1. 中转作用(结构型):通过中介者提供的中转作用,各同事间不需要显示引用其他对象,需要通信时,通过中介者通信即可;
    2. 协调作用(行为型):中介者可以更进一步对同事之间的关系进行封装,同事可以一致的和中介者进行交互,具体怎么做由中介者根据封装在自身内部的协调逻辑,进行下一步的处理,将同事成员之间的关系行为进行分离和封装;
  • 有如下角色:
    1. Mediator(抽象中介者): 定义一个接口,用于与各同事对象之间的通信;
    2. ConcreteMediator(具体中介者): 通过协调各个同事对象来实现协作行为,了解并维护它对各个同事对象的引用;
    3. Colleague(抽象同事类): 抽象同事类定义各同事的公有方法;
    4. ConcreteCollegue(具体同事类): 每一个同事对象都引用一个终结者对象,在与其他同事通信时,先于中介者通信,通过中介者来间接完成与其他同事类的通信;
6.2. 模式结构

Java 设计模式_第41张图片

6.3. 模式优缺点
  1. 优点:
    1. 简化了对象之间的交互
    2. 将各同事解耦
    3. 减少子类生成
    4. 复杂对象之间的交互,通过引入中介者,可以简化各同事类的设计和实现
  2. 缺点:
    1. 具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护
    2. 中介者承担了较多的责任,一旦终结者出现了问题,整个系统就会受到影响

7. 备忘录模式

7.1. 定义

备忘录模式(Memento Pattern)def: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态;其别名为 Token;备忘录模式是一种 对象行为型模式

有一下角色:

  1. Originator(原发器): 原发器可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态;
  2. Memento(备忘录): 存储原发器的内部状态,根据原发器来决定保存哪些内部状态;
  3. Caretaker(负责人): 又称为管理者,负责保存备忘录,但是不能对备忘录的内容进行操作或检查;可以存储一个或多个备忘录对象;
7.2. 模式结构

Java 设计模式_第42张图片

7.3. 实例说明

某系统提供了用户信息操作模块,用户可以修改自己的各项信息,为了使操作过程更加人性化,使用备忘录模式对系统进行改进,使得用户在进行错误操作之后可以恢复到操作之前的状态;

Java 设计模式_第43张图片

7.4. 模式优缺点
  1. 优点:
    1. 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤;
    2. 实现了信息的封装,一个备忘录对象是一种原发器对象的表示,不会被其他代码改动,这种模式简化了原发器对象;
  2. 缺点:
    1. 资源消耗过大,每保存一次对象的状态都需要消耗内存资源;

8. 解释器模式

8.1. 定义

解释器模式(Interpreter Pattern)def: 定义语言的文法,并且建立一个解释器来解释该语言中的句子,“语言” 意思是使用规定格式和语法的代码,它是一种 类行为型模式

有以下角色:

  1. AbstractExpression(抽象表达式): 声明了抽象的解释操作,是所有终结符表达式和非终结符表达式的公共父类;
  2. TerminalExpression(终结符表达式): 终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例;
  3. NonterminalExpression(非终结符表达式): 非终结符表达式也是抽象表达式的子类,实现了文法中非终结符的解释操作,非终结符表达式中也可以包含终结符表达式,因此在其解释操作一般使用递归的方式来完成;
  4. Context(环境类): 又称为上下文类,用于存储解释器之间的一些全局信息,通常它临时存储了需要解释的语句;
  5. Client(客户类): 构造了表示该文法定义的语言中一个特定句子的抽象语法树(由非终结符表达式和终结符表达式实例组合而成)然后调用解释操作,实现对该句子的解释;
8.2. 模式结构

Java 设计模式_第44张图片

8.3. 实例说明

构造一个于妍姐实习,使得系统可以执行整数间的乘、除和求模运算;如用户输入 3 * 4 / 2 % 4,输出结果为 2;

Java 设计模式_第45张图片

  1. 抽象表达式类 Node(抽象节点)

    public interface Node {
      int interpret();
    }
    
  2. 终结符表达式类 ValueNode(值节点)

    public class ValueNode implements Node {
      private int value;
      public ValueNode(int value) {
        this.value = value;
      }
      @Override
      public int interpret() {
        return value;
      }
    }
    
  3. 抽象非终结符表达式类 SymbolNode(符号节点类)

    public abstract class SymbolNode implements Node {
      protected Node left;
      protected Node right;
      public SymbolNode(Node left, Node right) {
        this.left = left;
        this.right = right;
      }
    }
    
  4. 非终结符表达式类(乘法、除法、求模节点类)

    public class MulNode extends SymbolNode {
      public MulNode(Node left, Node right) {
        super(left, right);
      }
      @Override
      public int interpret() { // 把这里的符号换成对应的即可
        return super.left.interpret() * super.right.interpret();
      }
    }
    
  5. 解释器封装类 Calculator(计算器类)

    public class Calculator {
      private String statement;
      private Node node;
      public void setStatement(String statement) {
        this.statement = statement;
      }
      public void build() {
        Node left = null, right = null;
        Deque<Node> stack = new ArrayDeque<>();
        String[] statementArr = statement.split(" ");
        for (int i = 0; i < statementArr.length; i++) {
          if (statementArr[i].equalsIgnoreCase("*")) {
            left = stack.pop();
            int val = Integer.parseInt(statementArr[++i]);
            right = new ValueNode(val);
            stack.push(new MulNode(left, right));
          } else if (statementArr[i].equalsIgnoreCase("/")) {
            left = stack.pop();
            int val = Integer.parseInt(statementArr[++i]);
            right = new ValueNode(val);
            stack.push(new DivNode(left, right));
          } else if (statementArr[i].equalsIgnoreCase("%")) {
            left = stack.pop();
            int val = Integer.parseInt(statementArr[++i]);
            right = new ValueNode(val);
            stack.push(new ModNode(left, right));
          } else {
            stack.push(new ValueNode(Integer.parseInt(statementArr[i])));
          }
        }
        this.node = stack.pop();
      }
      public int compute() {
        return node.interpret();
      }
    }
    
  6. Client 客户类

    public class Client {
      public static void main(String[] args) {
        String statement = "3 * 4 / 2 % 4";
        Calculator calculator = new Calculator();
        calculator.setStatement(statement);
        calculator.build();
        int result = calculator.compute();
        System.out.println(statement + " = " + result);
      }
    }
    
8.4. 模式优缺点
  1. 优点:
    1. 易于改变和拓展文法
    2. 易于实现文法
    3. 增加了新的解释表达式的方式,符合开闭原则;
  2. 缺点:
    1. 对于复杂文法难以维护(每一条规则至少需要定义一个类)
    2. 执行效率低,使用了大量的循环和递归调用
    3. 应用场景有限,在软件开发中很少自定义文法规则;

9. 状态模式

9.1. 定义

状态模式(State Pattern)def: 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类;其别名为 状态对象,状态模式是一种 对象行为型模式

有以下角色:

  1. Context(环境类): 是拥有状态的对象,由于其状态存在多样性,且在不同状态下对象的行为有所不同,所以将状态独立出来形成单独的状态类;
  2. State(抽象状态类): 定义了一个接口以封装与环境类的一个特定状态相关的行为;声明了各种不同状态对应的方法;
  3. ConcreteState(具体状态类): 是抽象状态类的子类,实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同;
9.2. 模式结构

Java 设计模式_第46张图片

9.3. 实例说明

某 App 有一个抽奖活动,抽一次奖要扣除用户 50 积分,中奖概率为 10%,奖品数量有限,送完抽奖活动截至,活动有四个状态:可以抽奖、不能抽奖、发放奖品和领取奖品;

Java 设计模式_第47张图片

9.4. 模式优缺点
  1. 优点:
    1. 封装了转换规则
    2. 枚举可能的状态,在枚举状态之前需要确定状态种类
    3. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
    4. 允许状态转换逻辑和状态对象合成一体,而不是一个巨大的条件语句块
    5. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
  2. 缺点:
    1. 使用状态模式会使得系统中类和对象的个数增加
    2. 状态模式的结构和实现都较为复杂,使用不当将导致程序结构和代码混乱
    3. 违反了开闭原则,增加新的状态类时需要修改那些负责状态转换的源代码

10. 策略模式

10.1. 定义

策略模式(Strategy Pattern)def: 定义一系列算法,将每一个算法封装起来,并让他们可以相互替换,策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy);策略模式是一种 对象行为型模式

有如下角色:

  1. Context(环境类): 环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略;在环境类中维护一个对抽象策略类的引用实例,用于定义所采用的策略;
  2. Strategy(抽象策略类): 抽象策略类为所支持的算法声明了抽象方法,是所有策略类的父类,可以是抽象类,也可以是接口;
  3. ConcreteStrategy(具体策略类): 实现了在抽象策略类中定义的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理;
10.2. 模式结构

Java 设计模式_第48张图片

10.3. 实例说明

某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作;以排序操作为例,使用策略模式设计该数组操作类,可以根据需要选择冒泡排序、选择排序、或插入排序,也能够灵活地增加新的排序算法;

Java 设计模式_第49张图片

10.4. 模式优缺点
  1. 优点:
    1. 符合开闭原则
    2. 策略模式提供了管理相关的算法族的办法
    3. 策略模式提供了可以替换继承关系的办法
    4. 使用策略模式可以避免使用多重条件转移语句
  2. 缺点:
    1. 策略模式只适用于客户端知道所有的算法或行为的情况
    2. 策略模式将造成产生很多策略类和对象,可以通过享元模式在一定程度上减少对象的数量

11. 职责链模式

11.1. 定义

职责链模式(Chain of Responsibility Pattern)def: 避免请求发送者与接收者耦合在一起,让多个对象都有可能接受请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止;又称为 责任链模式; 是一种 对象行为型模式;

  • 职责链可以是一条直线、一个环、或者一个树形结构,最常见的职责链是直线型的,即沿着一条单向的链来传递请求;
  • 有以下角色:
    1. Handler(抽象处理者): 定义了一个处理请求的接口,一般设计为抽象类;
    2. ConcreteHandler(具体处理者): 处理用户请求,实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,查看是否有相应的处理权限,可以处理请求就处理它,否则将请求转发给后继者;
    3. Client
11.2. 模式结构

Java 设计模式_第50张图片

11.3. 实例说明

有一个的采购审批 OA 系统,由采购员采购教学器材,如果:

  1. 金额 ≤ 5000,由教学主任审批
  2. 金额 ≤ 10000,由院长审批
  3. 金额 ≤ 30000,由副校长审批
  4. 金额 > 30000,由校长审批

Java 设计模式_第51张图片

11.4. 模式优缺点
  1. 优点:
    1. 降低耦合度,无需知道是哪一个对象处理请求,只需要知道请求会被处理即可
    2. 可简化对象的相互连接
    3. 增强给对象指派责任的灵活性
    4. 增加新的请求处理类很方便
  2. 缺点:
    1. 不能保证请求一定被接收,可能到职责链末端请求都得不到处理
    2. 对于比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响;
    3. 职责链设置不当,可能会造成循环调用,甚至导致系统陷入死循环;

你可能感兴趣的:(设计模式,uml)