《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中实现单件模式需要私有的构造器、一个静态方法和一个静态变量
  • 确定在性能和资源上的限制,然后小心地选择适当的方案来实现单件,以解决多线程的问题
  • 如果使用多个类加载器,可能导致单件失效而产生多个实例

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

tailwindcss 前端 css 框架 无需写css 快速构建页面

版本:VUE3 TS 框架 vite 文章中使用tailwindcss 版本: ^3.4.17 简介: Tailwind CSS 一个CSS 框架,提供组件化的样式,直接在HTML 中编写样式,无需额外自定义CSS ,快速! 简洁&#…

给小米/红米手机root(工具基本为官方工具)——KernelSU篇

目录 前言准备工作下载刷机包xiaomirom下载刷机包【适用于MIUI和hyperOS】“hyper更新”微信小程序【只适用于hyperOS】 下载KernelSU刷机所需程序和驱动文件 开始刷机设置手机第一种刷机方式【KMI】推荐提取boot或init_boot分区 第二种刷机方式【GKI】不推荐 结语 前言 刷机需…

路由器的WAN口和LAN口有什么区别?

今时今日,移动终端盛行的时代,WIFI可以说是家家户户都有使用到的网络接入方式。那么路由器当然也就是家家户户都不可或缺的设备了。而路由器上的两个实现网络连接的基础接口 ——WAN 口和 LAN 口,到底有什么区别?它们的功能和作用…

【Open X-Embodiment】简单数据下载与预处理

文章目录 1. RLDS Dataset2. 处理成numpy格式3. 存储桶 1. RLDS Dataset 从 Octo 里面找到数据下载的代码 rlds_dataset_mod github 按照官网代码配置环境后,修改 prepare_open_x.sh,相当于只用 gsutil 下载数据: DOWNLOAD_DIR/mnt/data…

神经网络八股(1)

1.什么是有监督学习,无监督学习 有监督学习是带有标签的,无监督学习是没有标签的,简单来说就是有监督学习的输入输出都是固定的,已知的,无监督学习输入是已知的,输出是不固定的,无监督学习是通…

达梦:开发 ODBC配置指南

目录 达梦数据库DM8 ODBC配置指南(Linux环境)ODBC一、环境准备二、核心配置步骤1. 安装unixODBC2. 配置ODBC驱动(odbcinst.ini)3. 配置数据源(odbc.ini) 三、连接测试与验证1. 使用isql工具测试2. 执行基础…

Python游戏编程之赛车游戏6-1

通过Python的pygame模块可以实现赛车游戏,如图1所示。 图1 赛车游戏 从图1中可以看出,玩家通过键盘的左右键操作蓝色汽车躲避红色汽车的撞击,每成功躲避过一辆红色汽车,则玩家得一分。当蓝色汽车被红色汽车撞击后,游戏…

【Linux网络】序列化、守护进程、应用层协议HTTP、Cookie和Session

⭐️个人主页:小羊 ⭐️所属专栏:Linux 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 1、序列化和反序列化2、守护进程2.1 什么是进程组?2.2 什么是会话? 3、应用层协议HTTP3.1 HTTP协议3.2 HT…

【Java消息队列】应对消息丢失、重复、顺序与积压的全面策略

应对消息丢失、重复、顺序与积压的全面策略 引言kafka消息丢失生产者消费者重复消费顺序消费消息积压生产者消费者其他RabbitMQ消息丢失生产者事务机制,保证生产者发送消息到 RabbitMQ Server发送方确认机制,保证消息能从交换机路由到指定队列保证消息在 RabbitMQ Server 中的…

Windows 上源码安装 FastGPT

FastGPT 是一个强大的 AI RAG 平台,值得我们去学习了解。与常见的 Python 体系不同,Fast GPT 采用 Node.js/Next.js 平台(对于广大 JS 开发者或前端开发者比较亲切友好),安装或部署比较简单。虽然一般情况下推荐简单的…

【HeadFirst系列之HeadFirstJava】第5天之超强力方法 —— 从战舰游戏到循环控制

编写程序:超强力方法 —— 从战舰游戏到循环控制 在《Head First Java》的第五章节中,作者通过一个简单的战舰游戏示例,深入讲解了如何编写Java程序,并重点介绍了方法和循环控制的使用。这一章节的核心思想是:通过模块…

软件单元测试的技术要求

文章目录 一、软件单元测试的概念二、测试对象三、测试目的四、进入条件五、测试内容六、测试环境七、测试实施方一、软件单元测试的概念 单元测试(Unit Testing),是指对软件中的最小可测试单元进行测试验证。单元测试是白盒测试,主要依据软件详细设计和软件代码进行,不仅…

‌挖数据平台对接DeepSeek推出一键云端部署功能:API接口驱动金融、汽车等行业智能化升级

云端部署 引言:当数据生产力遇上云端智能化 2025年2月23日,国内领先的数据服务商挖数据平台宣布与人工智能巨头DeepSeek达成战略合作,正式推出“一键云端部署”功能。这一功能以API(应用程序接口)为核心,通…

QPainter绘制3D 饼状图

先展示图片 核心代码如下&#xff1a; pie.h #ifndef Q3DPIE_H #define Q3DPIE_H#include <QtGui/QPen> #include <QtGui/QBrush>class Pie { public:double value; QBrush brush; QString description; double percentValue;QString p…

VMWare安装Debian操作系统

参考链接 https://blog.csdn.net/weixin_61536532/article/details/129778310 注意 如果希望折腾Linux&#xff0c;建议缺省使用英语。在极端情况下&#xff0c;系统可能会只能输出ASCII码&#xff0c;使用中文可能会导致无法正常打印log 本文使用VMWare WorkStation Pro&a…

Compose 常用UI组件

Compose 常用UI组件 概述Modifier 修饰符常用Modifier修饰符作用域限定Modifier Modifier 实现原理Modifier.Element链的构建链的解析 常用基础组件文字组件图片组件按钮组件选择器对话框进度条 常用布局组件线性布局帧布局 列表组件 概述 Compose 预置了很多基础组件&#xf…

基于Python+django+mysql旅游数据爬虫采集可视化分析推荐系统

2024旅游推荐系统爬虫可视化&#xff08;协同过滤算法&#xff09; 基于Pythondjangomysql旅游数据爬虫采集可视化分析推荐系统 有文档说明 部署文档 视频讲解 ✅️基于用户的协同过滤推荐算法 卖价就是标价~ 项目技术栈 Python语言、Django框架、MySQL数据库、requests网络爬虫…

R 语言科研绘图 --- 散点图-汇总

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…

3分钟idea接入deepseek

DeepSeek简介 DeepSeek 是杭州深度求索人工智能基础技术研究有限公司开发的一系列大语言模型&#xff0c;背后是知名量化资管巨头幻方量化3。它专注于开发先进的大语言模型和相关技术&#xff0c;拥有多个版本的模型&#xff0c;如 DeepSeek-LLM、DeepSeek-V2、DeepSeek-V3 等&…

【数据结构】(12) 反射、枚举、lambda 表达式

一、反射 1、反射机制定义及作用 反射是允许程序在运行时检查和操作类、方法、属性等的机制&#xff0c;能够动态地获取信息、调用方法等。换句话说&#xff0c;在编写程序时&#xff0c;不需要知道要操作的类的具体信息&#xff0c;而是在程序运行时获取和使用。 2、反射机制…