🚀 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 天 · 核心总结(极简背诵版)
-
六大原则速记:
单开里,依接迪 单一职责 / 开闭 / 里氏替换 / 依赖倒置 / 接口隔离 / 迪米特 -
单例模式四实现:
java饿汉式:类加载创建,简单安全 懒汉式:延迟加载,需同步 双重检查锁:⭐ 延迟 + 安全 + 高性能(记得 volatile!) 枚举单例:⭐ 最安全,防反射/序列化破坏 -
工厂模式对比:
简单工厂:一个工厂类,违反开闭 工厂方法:每个产品一个工厂,符合开闭(推荐) 抽象工厂:创建产品族(进阶) -
生产建议:
- ✅ 单例优先用枚举,其次双重检查锁
- ✅ 工厂模式优先工厂方法,便于扩展
- ❌ 避免滥用单例(全局状态难测试)
明天预告 :🏗️ 设计模式(建造者、原型) ------ 复杂对象创建方案!
- 建造者模式:链式调用构建复杂对象(如 UserBuilder)
- 原型模式:克隆对象,浅克隆 vs 深克隆
- 实战:用建造者模式创建"电脑配置",用原型模式实现"简历克隆"
准备好了吗?明天我们攻克"对象创建"的另外两种套路! 🔨✨