【设计模式笔记17】:单例模式1-模式分析

文章目录

      • [六、 单例模式](#六、 单例模式)
        • [1. 模式动机](#1. 模式动机)
        • [2. 模式定义](#2. 模式定义)
        • [3. 结构及角色分析](#3. 结构及角色分析)
        • [4. 模式实现步骤分解](#4. 模式实现步骤分解)
        • [5. 完整代码示例与验证](#5. 完整代码示例与验证)

六、 单例模式

1. 模式动机
  • 模式名称: 单例模式 (Singleton)
  • 核心动机 : 在整个软件系统中,对于某些类来说,有且只能有一个实例。保证这个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
  • 现实场景举例 :
    • 窗口管理器: 一个系统通常只有一个窗口管理器,负责管理所有窗口的层级、位置等。
    • 打印机假脱机服务: 系统中可以连接许多打印机,但只能有一个打印任务队列(假脱机服务)来统一管理和调度打印工作。
    • 配置文件读取类: 一个应用的配置信息通常是唯一的,用一个单例对象来读取和提供配置,可以避免重复读取和数据不一致。
  • 需要解决的问题: 我们怎样才能保证一个类只有一个实例,并且让系统中的任何部分都能方便地访问到这个唯一的实例呢?
  • 核心解决办法 : 让类自身负责保存它的唯一实例。这个类必须保证没有其它实例被创建,并且它可以提供一个方法来访问该实例。
2. 模式定义
  • 定义: 单例模式(Singleton Pattern)确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类被称为单例类,它提供全局访问的方法。
  • 三大要点 :
    1. 唯一实例: 某个类只能有一个实例。这是单例模式的根本。
    2. 自行创建: 它必须自行创建这个唯一的实例。创建的控制权在类的内部,而不是外部。
    3. 提供实例 : 它必须自行向整个系统提供这个实例。这意味着它需要提供一个全局访问点,并且在单例类的外部无法使用 new 关键字创建
  • 分类 : 单例模式是一种对象创建型模式
3. 结构及角色分析

单例模式的结构非常简单,只包含一个角色,即单例类 (Singleton)

图片描述 :一个名为Singleton的类图。其中包含:

  • 一个私有的、静态的、自身类型的属性 instance: Singleton
  • 一个私有的构造方法 Singleton()
  • 一个公有的、静态的、返回自身类型的方法 getInstance(): Singleton
    图中还有一个从类指向自身属性的菱形聚合符号,以及一个从 getInstance 方法指向 instance 属性的箭头,表示该方法会返回这个实例。
4. 模式实现步骤分解

为了实现单例模式的三个要点,我们需要一步步地构建这个类。

  • 第一步:私有化构造方法

    • 目的 : 防止外部通过 new 关键字随意创建实例。
    • 分析 : 要想在运行期间控制一个类的实例只有一个,首要任务就是要控制创建实例的地方。如果构造方法是公有的,那么在类的外部就可以随处 new,实例的数量就失控了。
    • 实现 : 将构造方法声明为 private
    java 复制代码
    private Singleton() {}
  • 第二步:提供获取实例的方法

    • 问题: 构造方法被私有化了,外部创建不了实例,也就无法调用对象的方法,这不行。
    • 解决: 单例类必须自己提供一个公有的方法,用来返回它内部创建好的那个唯一实例。
    java 复制代码
    // 初始设想,但有问题
    public Singleton getInstance() {}
  • 第三步:将获取实例的方法静态化

    • 问题: 上一步定义的是一个实例方法。要调用实例方法,必须先得有类的实例。这就陷入了一个"鸡生蛋,蛋生鸡"的死循环:为了获取实例,你得先有一个实例。
    • 解决 : 在方法上加上 static 关键字,使其成为一个类方法。这样就可以直接通过 类名.方法名() 的方式来调用,而不需要先得到类实例。
    java 复制代码
    public static Singleton getInstance() {}
  • 第四步:定义存储实例的静态属性

    • 问题 : getInstance() 方法的内部该如何实现?如果直接 return new Singleton();,那么每次调用都会创建一个新的实例,这违背了单例的初衷。
    • 解决: 在类内部用一个属性来记录(存储)已经创建好的那个唯一实例。当第一次创建后,就把实例保存在这个属性里,以后就直接复用这个实例,而不是重新创建。
    • 为什么要静态化 : 因为这个属性需要在静态方法 getInstance() 里面使用,而静态方法只能访问静态成员。所以这个存储实例的属性也必须是 static 的。
    java 复制代码
    private static Singleton instance = null;
  • 第五步:实现控制实例创建的逻辑

    • 目的 : 在 getInstance() 方法里实现对实例创建的控制。
    • 逻辑 :
      1. 先判断存储实例的静态属性 instance 是否有值(是否为 null)。
      2. 如果为 null,说明这是第一次调用,还没有创建过实例。此时,就 new 一个实例,并把它赋值给 instance 属性。
      3. 如果 instance 不为 null,说明之前已经创建过实例了。
      4. 最后,无论是否新建,都返回 instance 属性中存储的那个实例。
    java 复制代码
    public static Singleton getInstance() {
        // 先判断instance是否有值
        if (instance == null) {
            // 如果没有值,说明还没有创建过实例,那就创建一个
            // 并把这个实例设置给instance
            instance = new Singleton();
        }
        // 如果有值,或者是创建了值,那就直接使用
        return instance;
    }
5. 完整代码示例与验证

将以上所有步骤组合起来,就得到了一个基础的单例模式实现。

这种实现方式被称为"懒汉式",因为它是等到第一次需要时才创建实例

  • 完整实现
  • 验证代码
java 复制代码
// 验证逻辑
public static void main(String[] args) {
    Singleton singletonOne = Singleton.getInstance();
    Singleton singletonTwo = Singleton.getInstance();

    if (singletonOne.equals(singletonTwo)) {
        // 在Java中,对于引用类型,== 和 .equals() 在未重写时行为一致,都比较地址
        System.out.println("singletonOne 和 singletonTwo 代表的是同一个实例");
    } else {
        System.out.println("singletonOne 和 singletonTwo 代表的是不同实例");
    }
}
  • 预期结果: 程序会输出"singletonOne 和 singletonTwo 代表的是同一个实例",从而证明了单例模式的正确性。
相关推荐
20岁30年经验的码农几秒前
Java RabbitMQ 实战指南
java·开发语言·python
星轨初途14 分钟前
数据结构排序算法详解(2)——选择排序(附动图)
c语言·数据结构·经验分享·笔记·b树·算法·排序算法
gadiaola1 小时前
【计算机网络面试篇】HTTP
java·后端·网络协议·计算机网络·http·面试
S9037845971 小时前
为什么取模在除数等于2^n的时候可以用按位与替代?
java·tomcat·计算机外设·hibernate
phdsky1 小时前
【设计模式】抽象工厂模式
c++·设计模式·抽象工厂模式
7***37452 小时前
Java设计模式之工厂
java·开发语言·设计模式
程序员小白条2 小时前
你面试时吹过最大的牛是什么?
java·开发语言·数据库·阿里云·面试·职场和发展·毕设
charlie1145141912 小时前
勇闯前后端Week2:后端基础——Flask API速览
笔记·后端·python·学习·flask·教程
折翅嘀皇虫2 小时前
fastdds.type_propagation 详解
java·服务器·前端