JAVA重点基础、进阶知识及易错点总结(31)设计模式基础(单例、工厂)

🚀 Java 巩固进阶 · 第 31 天

主题:设计模式基础(单例、工厂)------ 架构思维的起点

📅 进度概览 :恭喜完成 JDK8+ 新特性阶段(25-30 天) !从今天起,进入 设计模式与注解阶段。设计模式是"前人总结的套路",掌握它能让你写出更优雅、更易维护的代码。

💡 核心价值

  • 代码质量:遵循设计原则,避免"面条代码",提升可读性与可维护性。
  • 面试通关:单例模式、工厂模式是初级→中级开发的必考题,手写线程安全单例是高频题。
  • 框架基石:理解 Spring Bean 的单例管理、工厂模式在框架中的广泛应用。
  • 设计思维:从"能跑就行"升级到"设计优雅",培养架构师视角。

一、设计模式六大原则:先懂原则再学模式 🎯

1. 原则速查表(⭐ 理解核心思想)

原则 核心思想 一句话解释 示例
单一职责 一个类只做一件事 "专业的人做专业的事" UserService 只处理用户逻辑,不碰订单
开闭原则 对扩展开放,对修改关闭 "加功能不改旧代码" 用策略模式替代 if-else,新增策略不改原逻辑
里氏替换 子类能替换父类 "儿子能替老爸上班" 方法参数用父类类型,传入子类对象仍正常工作
依赖倒置 依赖抽象,不依赖具体 "用接口编程,不用实现类" 依赖 DataSource 接口,而非 MySQLDataSource
接口隔离 接口要小而专 "拒绝胖接口" 拆分成 PrintInterface / ScanInterface,而非全能 Printer
迪米特法则 最少知道原则 "别管闲事" 对象只和直接朋友通信,不"跨层"调用

💡 记忆口诀

"单开里,依接迪" → 单一、开闭、里氏替换、依赖倒置、接口隔离、迪米特


二、单例模式:确保一个类只有一个实例 🔐

1. 为什么需要单例?

复制代码
🎯 场景:数据库连接池、配置管理器、日志对象
❌ 问题:如果每个地方都 new 一个,资源浪费 + 状态不一致
✅ 解决:单例模式,全局唯一实例,节约资源 + 统一状态

2. 四种实现方式对比(⭐ 必背)

java 复制代码
// ✅ 方式 1:饿汉式(类加载时创建,线程安全)
public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return instance; }
}
// 优点:简单,线程安全
// 缺点:类加载就创建,可能浪费资源(如果不用)

// ✅ 方式 2:懒汉式(首次使用时创建,需同步)
public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
// 优点:延迟加载
// 缺点:每次调用都同步,性能差

// ✅✅ 方式 3:双重检查锁(DCL,⭐ 推荐)
public class Singleton {
    // ⚠️ volatile 关键!防止指令重排序导致返回未初始化对象
    private static volatile Singleton instance;
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {  // 第二次检查
                    instance = new Singleton();  // 非原子操作:分配内存→初始化→返回引用
                }
            }
        }
        return instance;
    }
}
// 优点:延迟加载 + 线程安全 + 高性能(只同步一次)
// 关键:volatile 禁止重排序,确保其他线程拿到完全初始化的对象

// ✅✅ 方式 4:枚举单例(⭐ 最安全,推荐)
public enum Singleton {
    INSTANCE;
    public void doSomething() { /* 业务方法 */ }
}
// 优点:
// 1. 天然线程安全(JVM 保证)
// 2. 防止反射破坏(构造器会被反射调用?枚举不会)
// 3. 防止序列化破坏(readResolve 自动处理)
// 缺点:不支持延迟加载(但大部分场景不需要)

3. ⚠️ 单例的"破坏"与防御

java 复制代码
// 🐛 破坏 1:反射调用私有构造器
Constructor<Singleton> cons = Singleton.class.getDeclaredConstructor();
cons.setAccessible(true);
Singleton s2 = cons.newInstance();  // ❌ 创建了第二个实例!

// ✅ 防御:枚举单例 / 构造器内判断
private Singleton() {
    if (instance != null) {
        throw new RuntimeException("禁止反射创建单例");
    }
}

// 🐛 破坏 2:序列化反序列化
// 反序列化会创建新对象,绕过构造器
// ✅ 防御:实现 readResolve() 方法
private Object readResolve() {
    return instance;  // 返回已有实例
}

💡 生产建议

  • 简单场景 → 饿汉式(代码简洁)
  • 延迟加载 → 双重检查锁(记得 volatile)
  • 最安全 → 枚举单例(Effective Java 推荐)

三、工厂模式:解耦对象创建 🏭

1. 简单工厂(静态工厂方法)

java 复制代码
// ✅ 场景:根据参数创建不同对象,调用方无需知道具体类
public class AnimalFactory {
    public static Animal createAnimal(String type) {
        switch (type.toLowerCase()) {
            case "cat": return new Cat();
            case "dog": return new Dog();
            default: throw new IllegalArgumentException("未知类型");
        }
    }
}

// 调用
Animal a1 = AnimalFactory.createAnimal("cat");  // 解耦:调用方不 new Cat()

⚠️ 缺点:违反开闭原则,新增动物类型需修改工厂类

2. 工厂方法模式(⭐ 符合开闭原则)

java 复制代码
// 1. 抽象工厂接口
public interface AnimalFactory {
    Animal createAnimal();
}

// 2. 具体工厂
public class CatFactory implements AnimalFactory {
    public Animal createAnimal() { return new Cat(); }
}
public class DogFactory implements AnimalFactory {
    public Animal createAnimal() { return new Dog(); }
}

// 3. 调用
AnimalFactory factory = new CatFactory();
Animal a = factory.createAnimal();  // 新增动物只需加新工厂,不改旧代码 ✅

3. 🔍 工厂模式对比表

模式 核心思想 优点 缺点 适用场景
简单工厂 一个工厂类创建所有对象 调用方解耦,代码集中 违反开闭原则 对象类型少,不频繁扩展
工厂方法 每个对象对应一个工厂子类 符合开闭原则,易扩展 类数量增多 对象类型多,需频繁扩展
抽象工厂 工厂创建产品族(进阶) 支持产品族扩展 结构复杂 相关产品需一起创建(如 UI 主题)

💡 Spring 实践

  • BeanFactory / ApplicationContext 是工厂模式的极致应用
  • @Autowired 注入的 Bean,底层由 Spring 工厂创建 + 管理

四、🎯 今日实战任务:模式实战演练

任务 1:实现线程安全单例(双重检查锁)

java 复制代码
/**
 * 要求:
 * 1. 用双重检查锁实现 ConfigManager 单例
 * 2. 添加 volatile 关键字,理解其作用
 * 3. 编写多线程测试,验证是否只创建一个实例
 * 
 * 💡 调试技巧:
 * - 在构造器中打印 "Created: " + System.identityHashCode(this)
 * - 多线程调用 getInstance(),观察 hashCode 是否相同
 */

任务 2:用工厂方法创建交通工具

java 复制代码
/**
 * 业务场景:出行服务,支持汽车/自行车/飞机
 * 
 * 要求:
 * 1. 定义抽象类 Vehicle(抽象方法 start())
 * 2. 创建 Car/Bike/Plane 子类,实现 start()
 * 3. 定义 VehicleFactory 接口 + 具体工厂类
 * 4. 调用方通过工厂创建对象,调用 start()
 * 
 * 💡 挑战:
 * - 如果新增"高铁"类型,需要修改哪些代码?(验证开闭原则)
 */

任务 3:单例 + 工厂组合实战

java 复制代码
/**
 * 业务场景:数据库连接管理器(单例) + 连接工厂
 * 
 * 要求:
 * 1. ConnectionManager 用枚举单例实现
 * 2. 内部用工厂方法创建 MySQL/PostgreSQL 连接
 * 3. 提供 getConnection(String type) 方法
 * 
 * 💡 思考:
 * - 为什么连接管理器用单例?(全局唯一,管理连接池)
 * - 为什么连接创建用工厂?(解耦具体数据库驱动)
 */

任务 4:对比实验:单例性能测试

java 复制代码
/**
 * 要求:
 * 1. 分别实现饿汉式、懒汉式、双重检查锁、枚举单例
 * 2. 用 100 线程并发调用 10000 次 getInstance()
 * 3. 统计每种方案的耗时 + 实例数量(验证是否单例)
 * 
 * 💡 预期结论:
 * - 饿汉式:最快(无同步开销)
 * - 懒汉式(synchronized):最慢(每次同步)
 * - 双重检查锁:接近饿汉式(只同步一次)
 * - 枚举:与饿汉式相当(JVM 保证)
 */

📝 第 31 天 · 核心总结(极简背诵版)

  1. 六大原则速记

    复制代码
    单开里,依接迪
    单一职责 / 开闭 / 里氏替换 / 依赖倒置 / 接口隔离 / 迪米特
  2. 单例模式四实现

    java 复制代码
    饿汉式:类加载创建,简单安全
    懒汉式:延迟加载,需同步
    双重检查锁:⭐ 延迟 + 安全 + 高性能(记得 volatile!)
    枚举单例:⭐ 最安全,防反射/序列化破坏
  3. 工厂模式对比

    复制代码
    简单工厂:一个工厂类,违反开闭
    工厂方法:每个产品一个工厂,符合开闭(推荐)
    抽象工厂:创建产品族(进阶)
  4. 生产建议

    • ✅ 单例优先用枚举,其次双重检查锁
    • ✅ 工厂模式优先工厂方法,便于扩展
    • ❌ 避免滥用单例(全局状态难测试)

明天预告 :🏗️ 设计模式(建造者、原型) ------ 复杂对象创建方案!

  • 建造者模式:链式调用构建复杂对象(如 UserBuilder)
  • 原型模式:克隆对象,浅克隆 vs 深克隆
  • 实战:用建造者模式创建"电脑配置",用原型模式实现"简历克隆"

准备好了吗?明天我们攻克"对象创建"的另外两种套路! 🔨✨

相关推荐
不写八个5 小时前
PHP教程006:ThinkPHP项目入门
开发语言·php
helx825 小时前
SpringBoot中自定义Starter
java·spring boot·后端
ILYT NCTR5 小时前
SpringSecurity 实现token 认证
java
A.A呐5 小时前
【C++第二十三章】C++11
开发语言·c++
智算菩萨5 小时前
【Pygame】第8章 文字渲染与字体系统(支持中文字体)
开发语言·python·pygame
rleS IONS5 小时前
SpringBoot获取bean的几种方式
java·spring boot·后端
014-code5 小时前
Java SPI 实战:ServiceLoader 的正确打开方式(含类加载器坑)
java·开发语言
lifewange5 小时前
Go语言-开源编程语言
开发语言·后端·golang
程序员榴莲6 小时前
Javase(七):继承
java