2023-04-01  阅读(40)
原文作者:LoveLion 原文地址:https://blog.csdn.net/lovelion/article/details/7420883

3.1 单例模式的动机

对于一个软件系统的某些类而言,我们无须创建多个实例。举个大家都熟知的例子——Windows任务管理器,如图3-1所示,我们可以做一个这样的尝试,在Windows的“任务栏”的右键弹出菜单上多次点击“启动任务管理器”,看能否打开多个任务管理器窗口?如果你的桌面出现多个任务管理器,我请你吃饭,

202304012050345751.png(注:电脑中毒或私自修改Windows内核者除外)。通常情况下,无论我们启动任务管理多少次,Windows系统始终只能弹出一个任务管理器窗口,也就是说在一个Windows系统中,任务管理器存在唯一性。为什么要这样设计呢?我们可以从以下两个方面来分析:其一,如果能弹出多个窗口,且这些窗口的内容完全一致,全部是重复对象,这势必会浪费系统资源,任务管理器需要获取系统运行时的诸多信息,这些信息的获取需要消耗一定的系统资源,包括CPU资源及内存资源等,浪费是可耻的,而且根本没有必要显示多个内容完全相同的窗口;其二,如果弹出的多个窗口内容不一致,问题就更加严重了,这意味着在某一瞬间系统资源使用情况和进程、服务等信息存在多个状态,例如任务管理器窗口A显示“CPU使用率”为10%,窗口B显示“CPU使用率”为15%,到底哪个才是真实的呢?这纯属“调戏”用户,

202304012050351292.png,给用户带来误解,更不可取。由此可见,确保Windows任务管理器在系统中有且仅有一个非常重要。

202304012050355863.png

图3-1 Windows任务管理器

回到实际开发中,我们也经常遇到类似的情况,为了节约系统资源,有时需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,我们无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,我们可以通过单例模式来实现,这就是单例模式的动机所在。

3.2 单例模式概述

下面我们来模拟实现Windows任务管理器,假设任务管理器的类名为TaskManager,在TaskManager类中包含了大量的成员方法,例如构造函数TaskManager(),显示进程的方法displayProcesses(),显示服务的方法displayServices()等,该类的示意代码如下:

    class TaskManager
    {
         public TaskManager() {……} //初始化窗口
         public void displayProcesses()  {……} //显示进程
         public void  displayServices() {……} //显示服务
         ……
    }

为了实现Windows任务管理器的唯一性,我们通过如下三步来对该类进行重构:

(1) 由于每次使用new关键字来实例化TaskManager类时都将产生一个新对象,为了确保TaskManager实例的唯一性,我们需要禁止类的外部直接使用new来创建对象,因此需要将TaskManager的构造函数的可见性改为private,如下代码所示:

    private TaskManager() {……}

(2) 将构造函数改为private修饰后该如何创建对象呢?不要着急,虽然类的外部无法再使用new来创建对象,但是在TaskManager的内部还是可以创建的,可见性只对类外有效。因此,我们可以在TaskManager中创建并保存这个唯一实例。为了让外界可以访问这个唯一实例,需要在TaskManager中定义一个静态的TaskManager类型的私有成员变量,如下代码所示:

    private static TaskManager tm = null;

(3) 为了保证成员变量的封装性,我们将TaskManager类型的tm对象的可见性设置为private,但外界该如何使用该成员变量并何时实例化该成员变量呢?答案是增加一个公有的静态方法,如下代码所示:

    public static TaskManager getInstance()
    {
        if (tm == null)
        {
            tm = new TaskManager();
        }
        return tm;
    }

在getInstance()方法中首先判断tm对象是否存在,如果不存在(即tm == null),则使用new关键字创建一个新的TaskManager类型的tm对象,再返回新创建的tm对象;否则直接返回已有的tm对象。

需要注意的是getInstance()方法的修饰符,首先它应该是一个public方法,以便供外界其他对象使用,其次它使用了static关键字,即它是一个静态方法,在类外可以直接通过类名来访问,而无须创建TaskManager对象,事实上在类外也无法创建TaskManager对象,因为构造函数是私有的。

|    | 思考 为什么要将成员变量tm定义为静态变量?| | :-----: | :-----: | |     |  思考 为什么要将成员变量tm定义为静态变量? |

通过以上三个步骤,我们完成了一个最简单的单例类的设计,其完整代码如下:

    class TaskManager
    {
         private static TaskManager tm = null;
         private TaskManager() {……} //初始化窗口
         public void  displayProcesses() {……} //显示进程
         public void  displayServices() {……} //显示服务
         public static TaskManager getInstance()
         {
            if (tm == null)
            {
                tm = new TaskManager();
            }
            return tm;
        }
       ……
    }

在类外我们无法直接创建新的TaskManager对象,但可以通过代码TaskManager.getInstance()来访问实例对象,第一次调用getInstance()方法时将创建唯一实例,再次调用时将返回第一次创建的实例,从而确保实例对象的唯一性。

上述代码也是单例模式的一种最典型实现方式,有了以上基础,理解单例模式的定义和结构就非常容易了。单例模式定义如下:

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

单例模式是结构最简单的设计模式一,在它的核心结构中只包含一个被称为单例类的特殊类。单例模式结构如图3-2所示:

202304012050364584.png

单例模式结构图中只包含一个单例角色:

● Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文