【HeadFirst系列之HeadFirst设计模式】第6天之单件模式:独一无二的对象,如何优雅实现?

server/2025/2/22 18:40:41/

单件模式:独一无二的对象,如何优雅实现?

大家好!今天我们来聊聊设计模式中的单件模式(Singleton Pattern)。如果你曾经需要确保一个类只有一个实例,并且这个实例能够被全局访问,那么单件模式就是你的不二之选!本文基于《Head First 设计模式》的单件模式章节,通过生动的故事和 Java 代码示例,带你轻松掌握单件模式的精髓。

在这里插入图片描述


1. 单件模式是什么?

单件模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。单件模式的核心思想是控制对象的创建过程,避免重复创建实例,从而节省资源并保证数据的一致性。

适用场景

  • 需要全局唯一的对象,比如配置文件管理器、线程池、数据库连接池等。
  • 需要严格控制实例数量的场景。

2. 单件模式的实现

故事背景

小明开发了一个巧克力工厂系统,系统中有一个巧克力锅炉(ChocolateBoiler)类,用于控制巧克力的生产和填充。由于锅炉是唯一的资源,必须确保系统中只有一个锅炉实例。

问题出现

如果直接通过 new ChocolateBoiler() 创建锅炉对象,可能会导致多个实例被创建,从而引发资源冲突和数据不一致。

解决方案:单件模式

小明决定使用单件模式,确保系统中只有一个锅炉实例。

代码实现

基础版单件模式
java">public class ChocolateBoiler {// 静态变量,保存唯一实例private static ChocolateBoiler instance;// 私有构造函数,防止外部直接创建实例private ChocolateBoiler() {System.out.println("Creating a new ChocolateBoiler instance");}// 全局访问点public static ChocolateBoiler getInstance() {if (instance == null) {instance = new ChocolateBoiler();}return instance;}// 其他方法public void fill() {System.out.println("Filling the boiler with chocolate");}public void boil() {System.out.println("Boiling the chocolate");}public void drain() {System.out.println("Draining the boiled chocolate");}
}// 客户端代码
public class ChocolateFactory {public static void main(String[] args) {ChocolateBoiler boiler = ChocolateBoiler.getInstance();boiler.fill();  // 输出: Filling the boiler with chocolateboiler.boil();  // 输出: Boiling the chocolateboiler.drain(); // 输出: Draining the boiled chocolate// 再次获取实例ChocolateBoiler boiler2 = ChocolateBoiler.getInstance();System.out.println(boiler == boiler2); // 输出: true,说明是同一个实例}
}
优点
  • 确保一个类只有一个实例。
  • 提供全局访问点,方便使用。
缺点
  • 基础版单件模式在多线程环境下可能会创建多个实例。

3. 多线程环境下的单件模式

问题出现

如果多个线程同时调用 getInstance() 方法,可能会导致多个实例被创建。

解决方案:线程安全的单件模式

方法 1:加锁(synchronized)
java">public class ChocolateBoiler {private static ChocolateBoiler instance;private ChocolateBoiler() {System.out.println("Creating a new ChocolateBoiler instance");}// 加锁,确保线程安全public static synchronized ChocolateBoiler getInstance() {if (instance == null) {instance = new ChocolateBoiler();}return instance;}// 其他方法public void fill() {System.out.println("Filling the boiler with chocolate");}public void boil() {System.out.println("Boiling the chocolate");}public void drain() {System.out.println("Draining the boiled chocolate");}
}
方法 2:双重检查锁(Double-Checked Locking)
java">public class ChocolateBoiler {// 使用 volatile 关键字,确保 instance 的可见性private static volatile ChocolateBoiler instance;private ChocolateBoiler() {System.out.println("Creating a new ChocolateBoiler instance");}public static ChocolateBoiler getInstance() {if (instance == null) {synchronized (ChocolateBoiler.class) {if (instance == null) {instance = new ChocolateBoiler();}}}return instance;}// 其他方法public void fill() {System.out.println("Filling the boiler with chocolate");}public void boil() {System.out.println("Boiling the chocolate");}public void drain() {System.out.println("Draining the boiled chocolate");}
}
方法 3:静态内部类(推荐)
java">public class ChocolateBoiler {// 私有构造函数private ChocolateBoiler() {System.out.println("Creating a new ChocolateBoiler instance");}// 静态内部类,延迟加载且线程安全private static class SingletonHolder {private static final ChocolateBoiler INSTANCE = new ChocolateBoiler();}// 全局访问点public static ChocolateBoiler getInstance() {return SingletonHolder.INSTANCE;}// 其他方法public void fill() {System.out.println("Filling the boiler with chocolate");}public void boil() {System.out.println("Boiling the chocolate");}public void drain() {System.out.println("Draining the boiled chocolate");}
}
优点
  • 线程安全,且性能较高。
  • 延迟加载,只有在第一次调用 getInstance() 时才会创建实例。

4. 单件模式的注意事项

  1. 序列化问题
    如果单件类实现了 Serializable 接口,反序列化时可能会创建新的实例。可以通过重写 readResolve() 方法解决。

    java">protected Object readResolve() {return getInstance();
    }
    
  2. 反射攻击
    反射可以绕过私有构造函数创建实例。可以通过在构造函数中抛出异常来防止反射攻击。

    java">private ChocolateBoiler() {if (instance != null) {throw new IllegalStateException("Instance already created");}
    }
    
  3. 单件模式的滥用
    单件模式虽然好用,但不要滥用。过度使用单件模式会导致代码耦合性增加,难以测试和维护。


5. 总结

单件模式是确保一个类只有一个实例的有效方式,适用于需要全局唯一对象的场景。通过本文的讲解和代码示例,相信你已经掌握了单件模式的核心思想和实现方法。在实际开发中,记得根据具体需求选择合适的实现方式,并注意线程安全和反序列化等问题。


互动话题
你在项目中用过单件模式吗?遇到过哪些问题?欢迎在评论区分享你的经验!


http://www.ppmy.cn/server/169909.html

相关文章

基于springboot+vue的酒店管理系统的设计与实现

开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:…

三、动规_子数组系列

文章目录 子数组系列问题53. [最大子数组和](https://leetcode.cn/problems/maximum-subarray/description/)思路代码 918. [环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/description/)思路代码 152. [乘积最大子数组](https://leetcode.…

PHP脚本示例

/*** desc 清理产品商品无效数据* param Input $input* php think goods -m Clear -a clearGoods --endTime2020-01-01 -vvv* return void* author 陈龙* date 2024-04-02 9:45*/public function clearGoods(Input $input){$options $input->getOptions();$start_time mic…

如何监控和优化 MySQL 中的慢 SQL

如何监控和优化 MySQL 中的慢 SQL 前言一、什么是慢 SQL?二、如何监控慢 SQL?1. 启用慢查询日志启用方法:日志内容: 2. 使用 mysqldumpslow 分析日志 三、如何分析慢 SQL?1. 使用 EXPLAIN 分析执行计划使用方法&#x…

国产编辑器EverEdit - 如何在EverEdit中创建工程?

1 创建工程 1.1 应用场景 工程是一个文件及文件夹的集合,对于稍微有点规模的项目,一般都会包含多个文件,甚至还会以文件夹的形式进行分层管理多个文件,为了方便的管理这个项目,可以将这些文件和文件夹保存为一个工程。…

Spring Boot 项目开发流程全解析

目录 引言 一、开发环境准备 二、创建项目 三、项目结构 四、开发业务逻辑 1.创建实体类: 2.创建数据访问层(DAO): 3.创建服务层(Service): 4.创建控制器层(Controller&…

Java每日精进·45天挑战·Day20

第二部分:链表旋转 在数据结构中,链表是一种非常基础且重要的数据结构。它允许我们在不需要大量数据移动的情况下,在任意位置插入或删除元素。今天,我们将探讨一个链表相关的有趣问题:如何将链表向右旋转 k 个位置&am…

深入解析 Hydra 库:灵活强大的 Python 配置管理框架

深入解析 Hydra 库:灵活强大的 Python 配置管理框架 在机器学习、深度学习和复杂软件开发项目中,管理和维护大量的配置参数是一项具有挑战性的任务。传统的 argparse、json 或 yaml 方式虽然能管理部分配置,但随着项目规模的增长&#xff0c…