锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

Java 设计模式

时间:2022-09-17 06:30:00 电连接器插座的屏蔽构件

文章目录

  • 设计模式
    • 一、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
    }
    

image-20220515211925701

@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. 类图

有如下类图:

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

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

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. 类图

5. 开闭原则

5.1. 定义

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

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

5.2. 类图

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. 类图

三、创建型模式

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. 模式结构

2.3. 实例说明

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

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

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

3. 工厂方法模式

3.1. 定义

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

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

3.2. 模式结构

3.3. 实例说明

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

4. 抽象工厂模式

4.1. 定义

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

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

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

4.2. 模式结构

4.3. 实例说明

5. 原型模式

5.1. 定义

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

包含如下角色:

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

5.2. 模式结构

5.3. 深拷贝与浅拷贝

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

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

  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(元器件数据手册、IC替代型号,打造电子元器件IC百科大全!
          

相关文章