《Head First设计模式》读书笔记 —— 单件模式

文章目录

    • 为什么需要单件模式
    • 单件模式典型实现剖析
    • 定义单件模式
    • 本节用例
    • 多线程带来的问题
      • 解决问题
      • 优化
    • Q&A
    • 总结

《Head First设计模式》读书笔记
相关代码: Vks-Feng/HeadFirstDesignPatternNotes: Head First设计模式读书笔记及相关代码

  • 用来创建独一无二的,只能有一个实例的对象的入场券

为什么需要单件模式

有些对象只能有一个实例

  • 线程池、缓存、对话框、设备的驱动程序的对象、注册表设置对象
    如果制造出多个实例,就会导致许多问题产生
  • 程序的行为异常、资源使用过量、结果不一致

为什么不去靠人为约定或全局变量实现该目标?

  • 人为约定:人为约定不一定能绝对严格地被遵守,且若存在更好方法,自然更愿意接受
  • 全局变量:必须在程序开始前就创建对象并将其赋值给一个全局变量,若某次执行时没用到则是资源的浪费。(不能实现“懒加载”)

单件模式典型实现剖析

public class Singleton {  
    private static Singleton uniqueInstance;  
  
    private Singleton() { }  
  
    public static Singleton getInstance() {  
        if (uniqueInstance == null) {  
            uniqueInstance = new Singleton();  
        }  
        return uniqueInstance;  
    }  
}

为什么会被这样设计?

  • 创建对象仅需简单的new,为了避免被多次创建,将类的构造器私有化。
    • 但是私有化后只有类内的代码才能实例化该类,而类不被实例化如何调用其构造器呢?(“鸡生蛋,蛋生鸡”)
  • 通过在类中设计一个静态方法,解决不实例化该类,而能调用其构造器。

延迟实例化(Lazy instantiate)

if (uniqueInstance == null) {  
	uniqueInstance = new Singleton();  
}  
return uniqueInstance;

当我们需要该实例时再去创建对象,如果不需要就永远不会产生。

定义单件模式

#HeadFirst设计模式6-单件模式

单件模式确保一个类只有一个实例,并提供一个全局访问点。

单件模式类图

  • 把类设计成自己管理的一个单独实例,同时也避免其他类再自行产生实例
  • 通过单间类事获取单件实例的唯一途径
  • 提供这个实例的全局访问点:当你需要实例时,响雷查询,它会返回单个实例
  • 延迟实例化的方式创建单件对资源敏感的对象特别重要

本节用例

某公司设计的巧克力锅炉控制器
需要避免糟糕的情况发生:排除未煮沸的原料、已满情况下继续加原料、未放原料就空烧

public class ChocolateBoiler {  
    private boolean empty;  
    private boolean boiled;  
    public ChocolateBoiler() {  
        empty = true;  
        boiled = false;  
    }  
    public void fill() {  
        if (isEmpty()) {  
            empty = false;  
            boiled = false;  
            // 填充原料  
        }  
    }    public void drain() {  
        if (isEmpty() && isBoiled()) {  
            empty = true;  
        }  
    }    public void boil() {  
        if (!isEmpty() && !isBoiled()) {  
            boiled = true;  
        }  
    }  
    private boolean isBoiled() {  
        return boiled;  
    }  
  
    private boolean isEmpty() {  
        return empty;  
    }  
}

为了避免同时存在多个实例带来问题,对其进行单件化改造

public class ChocolateBoiler {  
	private static ChocolateBoiler uniqueInstance;  
    private boolean empty;  
    private boolean boiled;  
    
    public static ChocolateBoiler getInstance() {
	    if (uniqueInstance == null) {
		    uniqueInstance = new ChocolateBoiler();
	    }
	    return uniqueInstance;
    }
    
    private ChocolateBoiler() {  
        empty = true;  
        boiled = false;  
    }  
}

多线程带来的问题

当有多个熔炉实例时,可能会发生我们先前想避免的问题,但上面已有的单例模式可能会在多线程情况下创建出不只一个实例。

解决问题

只要把getInstance()变成同步(synchronized)方法,多线程灾难几乎就可以轻易地解决了

public class Singleton {  
    private static Singleton uniqueInstance;  
  
    private Singleton() { }  
  
    public static synchronized Singleton getInstance() {  
        if (uniqueInstance == null) {  
            uniqueInstance = new Singleton();  
        }  
        return uniqueInstance;  
    }  
}

问题:同步会降低性能,且当实例被正确初始化后,后续就不用再同步了,同步将会彻底成为累赘

优化

  1. getInstance()的性能对应用程序不是很关键,就什么都别做
    • getInstance()被频繁使用时,就需要考虑优化了
  2. 使用“急切”创建实例,而不用延迟实例化的做法
    • 频繁使用getInstance()时可用
    • 创建和运行时负担不繁重时可用
    public class Singleton {  
        private static Singleton uniqueInstance = new Singleton();  
      
        private Singleton() { }  
      
        public static synchronized Singleton getInstance() {  
            return uniqueInstance;  
        }  
    }
    
  3. “双重检查枷锁”,在getInstance()中减少使用同步
    • 首先检查是否实例已经创建了,如果尚未创建才会同步,这样就可以实现“只有第一次同步”了
    private volatile static Singleton uniqueInstance;  
    
    private Singleton() { }  
    
    public static Singleton getInstance() {  
    	if (uniqueInstance == null) {  
            synchronized (Singleton.class) {  
                if (uniqueInstance == null) {  
                    uniqueInstance = new Singleton();  
                }  
            }    
        }    
        return uniqueInstance;  
    }
    
    • volatile关键字确保当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量

Q&A

Q:为何不创建一个类,将其所有方法和变量都定义为静态的,直接把类当作一个单件?
A:

  • 如果类自给自足,而且不依赖于复杂的初始化,可以这么做。
  • 但是因为静态初始化的控制权是在Java手上,这么做有可能导致混乱,特别是有许多类牵涉其中时。这么做容易造成一些不易发现的和初始化次序有关的bug。
  • 建议使用对象的单件,比较保险。

Q:类加载器(class loader),听说两个类加载器可能有机会创建自己的单件实例
A:是的

  • 每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次。
  • 如果这种情况发生在单件上,就会产生多个单件并存的怪异现象。
  • 当程序有多个类加载器而你又使用了单件模式时,需要注意。解决方案:自行指定类加载器,并指定同一个类加载器

Q:可不可以继承单件类?
A:继承单间类面临的问题:构造器是私有的。不能用私有构造器来扩展类。所以你必须把单件的构造器改成公开的或受保护的,而这样:

  • 就算不上“真正的”单件了,因为别的类也可实例化它
  • 单件的实现是利用静态变量,直接继承会导致所有的派生类共享同一个实例变量,可能并非预期效果,需要实现注册表(Registry)功能

Q:为什么全局变量比单件模式差?
A:

  • Java中,全局变量基本上就是对对象的静态引用。这种情况下使用全局变量就会有缺点,例如前文提到的急切实例化v.s.延迟实例化。
  • 但要记住该模式的目的“确保只有一个实例并提供全局访问。全局变量可以提供全局访问,但不能确保只有一个实例。全局变量也会变相鼓励开发者用许多全局变量指向许多小对象来造成命名空间的污染。

总结

OO基础

  • 抽象
  • 封装
  • 多态
  • 继承

OO原则

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,不针对实现编程
  • 为交互对象之间的松耦合设计而努力
  • 对扩展开放,对修改关闭
  • 依赖抽象,不要依赖具体类

OO模式

  • 单件模式——确保一个类只有一个实例,并提供全局访问。

要点

  • 单件模式确保程序中的类最多只有一个实例
  • 单件模式也提供访问这个实例的全局点
  • Java中实现单件模式需要私有的构造器、一个静态方法和一个静态变量
  • 确定在性能和资源上的限制,然后小心地选择适当的方案来实现单件,以解决多线程的问题
  • 如果使用多个类加载器,可能导致单件失效而产生多个实例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/975958.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Git常见命令--助力开发

git常见命令: 创建初始化仓库: git 将文件提交到暂存区 git add 文件名 将文件提交到工作区 git commit -m "注释(例如这是发行的版本1)" 文件名 查看状态 如果暂存区没有文件被提交显示: $ git status On…

【每日八股】Redis篇(二):数据结构

Redis 数据类型? 主要有 STRING、LIST、ZSET、SET 和 HASH。 STRING String 类型底层的数据结构实现主要是 SDS(简单动态字符串),其主要应用场景包括: 缓存对象:可以用 STRING 缓存整个对象的 JSON&…

LLM大语言模型私有化部署-使用Dify的工作流编排打造专属AI诗词数据分析师

背景 前面的文章通过 Ollama 私有化部署了 Qwen2.5 (7B) 模型,然后使用 Docker Compose 一键部署了 Dify 社区版平台。 LLM大语言模型私有化部署-使用Dify与Qwen2.5打造专属知识库:在 Dify 平台上,通过普通编排的方式,创建了基于…

Linux虚拟机快照

快照管理 如果在使用虚拟机系统的时候(比如linux),想回到原先的某一个状态,也就是说担心可能有些误操作造成系统异常,需要回到原先某个正常运行的状态 示例: 状态A和状态B处各保存了快照,运行到状态C时发生异常&…

【异常错误】pycharm debug view变量的时候显示不全,中间会以...显示

异常问题: 这个是在新版的pycharm中出现的,出现的问题,点击view后不全部显示,而是以...折叠显示 在setting中这么设置一下就好了: 解决办法: https://youtrack.jetbrains.com/issue/PY-75568/Large-stri…

快速入门Springboot+vue——MybatisPlus多表查询及分页查询

学习自哔哩哔哩上的“刘老师教编程”,具体学习的网站为:7.MybatisPlus多表查询及分页查询_哔哩哔哩_bilibili,以下是看课后做的笔记,仅供参考。 多表查询 多表查询[Mybatis中的]:实现复杂关系映射,可以使…

vscode 配置 Copilot 提示GHE.com连接失败

步骤一:打开设置并进入 settings.json 点击菜单栏中的 “文件” -> “首选项” -> “设置”。 在搜索设置栏中输入 “Copilot: Advanced”。 点击搜索结果下方的 “在 settings.json 中编辑” 链接,这会打开 settings.json 文件。 步骤二&#…

基于拼接的宏基因组全流程

下面是基于组装的宏基因组数据分析流程 目录 基本流程介绍 megahit组装 什么是N50? 基于拼接结果的基因预测 cdhit去冗余 功能注释 宏基因组的分箱操作 分箱的目的: 分箱的原理: 基本流程介绍 单独对每个样本进行基因集组装,得到genome1,2,3…

基于javaweb的SpringBoot酒店管理系统设计和实现(源码+文档+部署讲解)

技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

Grok 3.0 Beta 版大语言模型评测

2025年2月17日至18日,全球首富埃隆马斯克(Elon Musk)携手其人工智能公司xAI,在美国重磅发布了Grok 3.0 Beta版。这款被誉为“迄今为止世界上最智能的语言模型”的AI,不仅集成了先进的“DeepSearch”搜索功能&#xff0…

【R语言】绘图

一、散点图 散点图也叫X-Y图,它将所有的数据以点的形式展现在坐标系上,用来显示变量之间的相互影响程度。 ggplot2包中用来绘制散点图的函数是geom_point(),但在绘制前需要先用ggplot()函数指定数据集和变量。 下面用mtcars数据集做演示&a…

php session数据存储位置选择

PHP session 数据的存储位置可以通过配置文件或者代码来进行设置。默认情况下,session 数据是存储在服务器的文件系统中的。你可以将 session 数据存储在其他地方,例如数据库、缓存等。 基础概念 PHP session默认情况下将数据存储在服务器端的临时文件中…

保姆级! 本地部署DeepSeek-R1大模型 安装Ollama Api 后,Postman本地调用 deepseek

要在Postman中访问Ollama API并调用DeepSeek模型,你需要遵循以下步骤。首先,确保你有一个有效的Ollama服务器实例运行中,并且DeepSeek模型已经被加载。 可以参考我的这篇博客 保姆级!使用Ollama本地部署DeepSeek-R1大模型 并java…

Windows桌面系统管理5:Windows 10操作系统注册表

Windows桌面系统管理0:总目录-CSDN博客 Windows桌面系统管理1:计算机硬件组成及组装-CSDN博客 Windows桌面系统管理2:VMware Workstation使用和管理-CSDN博客 Windows桌面系统管理3:Windows 10操作系统部署与使用-CSDN博客 Wi…

臻识相机,华夏相机,芊熠车牌识别相机加密解密

臻识,华夏,芊熠这三种车牌识别相机解密我都试过了,可以正常解密成功,其它品牌我暂时没有测试。超级简单,免费的,白嫖无敌! 流程: ①:先导出配置文件,例如我以…

RK Android11 WiFi模组 AIC8800 驱动移植流程

RK Android WiFi模组 AIC8800 驱动移植流程 作者:Witheart更新时间:20250220 概要:本文介绍了基于 AIC8800D40 芯片的 WiFi6 模组 BL-M8800DS2-40 在 RK3568 平台上的驱动移植流程。主要涉及环境搭建、驱动代码分析、设备树修改、驱动编译配…

Unity Shader Graph 2D - Procedural程序化图形循环加载进度效果

前言 在游戏中进度加载的效果是一种常见的效果,可以告诉玩家当前游戏处于一个资源加载的状态,这样玩家就能理解游戏不是卡住了或者是出现Bug了,而是正在进行一些数据的处理准备进入下一个场景。 创建一个LineLoading的Shader Graph文件,对应创建一个材质球,然后在…

蓝桥杯备考:贪心算法之矩阵消除游戏

这道题是牛客上的一道题,它呢和我们之前的排座位游戏非常之相似,但是,排座位问题选择行和列是不会改变元素的值的,这道题呢每每选一行都会把这行或者这列清零,所以我们的策略就是先用二进制把选择所有行的情况全部枚举…

Java网络编程封装

系列文章目录 Java知识点 文章目录 系列文章目录👉前言👉一、封装的目标👉二、套接字层封装👉壁纸分享👉总结 👉前言 Java 网络编程封装原理主要围绕着将底层的网络通信细节隐藏起来,提供简洁…

百度首页上线 DeepSeek 入口,免费使用

大家好,我是小悟。 百度首页正式上线了 DeepSeek 入口,这一重磅消息瞬间在技术圈掀起了惊涛骇浪,各大平台都被刷爆了屏。 百度这次可太给力了,PC 端开放仅 1 小时,就有超千万人涌入体验。这速度,简直比火…