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

news/2025/2/23 0:58:25

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

大家好!今天我们来聊聊设计模式中的单件模式(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 chocolate
        boiler.boil();  // 输出: Boiling the chocolate
        boiler.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.niftyadmin.cn/n/5862887.html

相关文章

Linux·spin_lock的使用

自旋锁 内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择: 一个是原地等待一个是挂起当前进程,调度其他进程执行(睡眠) Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方…

Excell 代码处理

文章目录 Excell 代码处理cvc格式xlsl格式小结 Excell 代码处理 有时候要对excell进行分析,或者数据的导入导出,这个时候如果可以用代码读写分析操作那么会方便很多 cvc格式 CSV(Comma-Separated Values,逗号分隔值)是…

Visual studio 2022 将打开文件的方式由单击改为双击

1. 打开vs2022,选择Tools -> Options打开Options设置页面 2. 在左侧依次展开Environment, 选择Tabs and Windows 3. 在右侧面板往下拖拽滚动条,找到Preview Tab section, unchecked "Preview selected files in Solution Explorer (Altclick t…

pikachu之CSRF防御:给你的请求加上“网络身份证”

CSRF防御:给你的请求加上“网络身份证” 上集回顾 ​在上一章节中,我们化身“遥控黑客”,用GET请求和POST表单把CSRF漏洞玩得风生水起,体验了“隔空改签名”等骚操作。今天,我们将从攻击者变身防御者,揭秘…

MySQL八股学习笔记

文章目录 一、MySQL结构1.宏观结构1.1.Server层1.2.存储引擎层 2.建立链接-连接器3.查询缓存4.解析SQL-解析器(1)词法分析(2)语法分析 5.执行SQL5.1.预处理器 prepare5.2.优化器 optimize5.3.执行器 execute(1&#xf…

Day15-后端Web实战-登录认证——会话技术JWT令牌过滤器拦截器

目录 登录认证1. 登录功能1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 测试 2. 登录校验2.1 问题分析2.2 会话技术2.2.1 会话技术介绍2.2.2 会话跟踪方案2.2.2.1 方案一 - Cookie2.2.2.2 方案二 - Session2.2.2.3 方案三 - 令牌技术 2.3 JWT令牌2.3.1 介绍2.3.2 生成和校…

WARNING: pip is configured with locations that require TLS/SSL

一、报错 WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available. 二、故障排查 1、检查Python3环境中的SSL支持: 打开终端或命令提示符,进入你的Python环境目录。 运行以下命令来查看可用的SSL版本: [r…

开源且免费的CMS系统有哪几个可以放心用?

既开源又免费的两全其美的CMS不多见,不过总会存在一些个例,给用户们带来更具有建设性的选择,以下是一些开源免费且值得信赖的CMS系统,可以根据你的需求选择合适的平台: 1、WordPress ▷ 特点:全球最流行的…