当前位置: 首页 > article >正文

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

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

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

在这里插入图片描述


1. 单件模式是什么?

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

适用场景

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

2. 单件模式的实现

故事背景

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

问题出现

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

解决方案:单件模式

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

代码实现

基础版单件模式
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)
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)
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:静态内部类(推荐)
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() 方法解决。

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

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


5. 总结

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


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


http://www.kler.cn/a/556758.html

相关文章:

  • eBPF驱动的实时内核安全防护体系:构建零日漏洞免疫的云原生基础设施
  • Git介绍和使用
  • 使用爬虫获得亚马逊按关键字搜索商品的实战指南
  • qt:输入控件操作
  • Android Coil3缩略图、默认占位图placeholder、error加载错误显示,Kotlin(1)
  • 力扣的第34题 在排序数组中查找元素的第一个和最后一个位置
  • 设计模式教程:迭代器模式(Iterator Pattern)
  • 日期类(完全讲解版)
  • 【深度学习】手写数字识别任务
  • Go 之 Beego 配置文件
  • iOS各个证书生成细节
  • Windows 快速搭建C++开发环境,安装C++、CMake、QT、Visual Studio、Setup Factory
  • 【git】工作流实战:从本地仓库到远程仓库,git pull 与git rebase使用讲解,案例解析
  • Docker安装Open WebUI教程
  • Spring AI + Ollama 实现调用DeepSeek-R1模型API
  • 30. 串联所有单词的子串
  • C++ 设计模式-观察者模式
  • 《代码随想录第三十九天》——背包问题二维、背包问题一维、分割等和子集
  • 精准测量PMD:OCI-V光矢量分析系统赋能光纤通信性能优化
  • 基于eBPF的智能诊断平台:实现云原生系统的自愈型运维体系