一文吃透Java设计模式:单例模式精讲+常用模式实战
目录一、重中之重单例模式Singleton Pattern1. 单例模式是什么通俗类比2. 单例模式的4种常用写法实战必掌握1饿汉式线程安全简单但可能浪费资源2懒汉式基础版线程不安全面试避坑3双重检查锁DCL推荐线程安全懒加载4静态内部类推荐线程安全懒加载更简洁3. 单例模式的避坑要点面试必问二、Java常用其他设计模式5种覆盖80%场景1. 工厂模式Factory Pattern—— 对象创建的“流水线”2. 代理模式Proxy Pattern—— 对象的“代言人”3. 装饰器模式Decorator Pattern—— 给对象“穿衣服”4. 观察者模式Observer Pattern—— 事件的“订阅-通知”机制5. 策略模式Strategy Pattern—— 算法的“灵活切换”三、设计模式核心总结新手必看做Java开发的同学一定绕不开「设计模式」——它就像编程中的“武林秘籍”是前辈们总结的代码设计经验能帮我们写出更简洁、可复用、易维护的代码。但很多新手一提设计模式就头疼尤其是单例模式各种写法、线程安全问题绕得人晕头转向更分不清不同设计模式的适用场景。本文全程避开晦涩术语延续“生活化类比可直接复制的实战代码避坑要点”的风格先重点拆解单例模式面试高频重中之重再讲解5个Java最常用的其他设计模式不管是新手入门、项目落地还是面试备考都能直接套用。核心逻辑设计模式不是“炫技工具”而是“问题解决方案”——不同设计模式对应不同的代码痛点学会“什么时候用、怎么用”比死记硬背写法更重要。一、重中之重单例模式Singleton Pattern单例模式是Java中最基础、最常用也最容易踩坑的设计模式没有之一。它的核心很简单但写法多样线程安全问题是面试必问考点。1. 单例模式是什么通俗类比核心定义保证一个类在整个程序运行过程中只创建一个实例对象并且提供一个全局唯一的访问入口避免频繁创建对象造成的资源浪费。类比我们生活中的“国家总统”——一个国家在同一时期只能有一位总统一个实例全国人民都通过统一的渠道访问入口找到总统不会出现多个总统并存的情况。适用场景工具类比如日志工具类、数据库连接工具类不需要多个实例全局共用一个即可配置类全局配置信息只需要加载一次多实例会造成配置混乱线程池、连接池这类资源创建成本高复用一个实例能提升性能。2. 单例模式的4种常用写法实战必掌握单例模式的写法有很多新手只需掌握4种懒汉式、饿汉式、双重检查锁、静态内部类其中双重检查锁和静态内部类是企业开发和面试中最常用的重点掌握。1饿汉式线程安全简单但可能浪费资源核心思路类加载时就创建实例不管后续是否使用天生线程安全类加载过程是线程安全的。// 饿汉式单例 public class SingletonHungry { // 1. 私有构造方法禁止外部new对象 private SingletonHungry() {} // 2. 类加载时就创建唯一实例饿汉提前创建 private static final SingletonHungry INSTANCE new SingletonHungry(); // 3. 提供全局访问入口 public static SingletonHungry getInstance() { return INSTANCE; } }实战要点优点写法简单线程安全无需额外处理并发问题缺点类加载时就创建实例若后续从未使用会浪费内存比如重量级的工具类适用场景实例创建成本低、一定会被使用的场景比如简单的日志工具类。2懒汉式基础版线程不安全面试避坑核心思路延迟加载只有在第一次调用getInstance()方法时才创建实例懒加载基础版线程不安全实际开发中不能直接用。// 懒汉式基础版线程不安全禁止直接使用 public class SingletonLazy { // 1. 私有构造方法 private SingletonLazy() {} // 2. 声明实例不提前创建 private static SingletonLazy instance; // 3. 全局访问入口第一次调用时创建实例 public static SingletonLazy getInstance() { // 问题多线程并发时会创建多个实例 if (instance null) { instance new SingletonLazy(); } return instance; } }实战要点优点延迟加载避免内存浪费缺点多线程并发时多个线程会同时进入if判断创建多个实例破坏单例原则避坑面试时若被问“懒汉式的问题”要能说出线程不安全的原因且明确表示这种写法不能用于生产环境。3双重检查锁DCL推荐线程安全懒加载核心思路在懒汉式基础上增加两层判断双重检查 volatile关键字解决线程安全问题同时保留懒加载的优势是企业开发中最常用的写法。// 双重检查锁DCL单例推荐使用 public class SingletonDCL { // 1. 私有构造方法 private SingletonDCL() {} // 2. 声明实例添加volatile关键字禁止指令重排 private static volatile SingletonDCL instance; // 3. 双重检查保证线程安全和懒加载 public static SingletonDCL getInstance() { // 第一次检查避免不必要的同步提高性能 if (instance null) { synchronized (SingletonDCL.class) { // 第二次检查防止多线程并发时多个线程进入同步代码块后重复创建实例 if (instance null) { instance new SingletonDCL(); } } } return instance; } }实战要点面试高频volatile关键字的作用禁止JVM指令重排因为instance new SingletonDCL()会被拆分为3步分配内存、初始化实例、赋值引用指令重排可能导致线程拿到“未初始化的实例”优点线程安全、懒加载、性能高只有第一次调用时会进入同步代码块适用场景绝大多数生产环境尤其是实例创建成本高、不一定会被使用的场景。4静态内部类推荐线程安全懒加载更简洁核心思路利用Java静态内部类的特性静态内部类不会随外部类加载而加载只有被调用时才加载实现懒加载和线程安全写法比双重检查锁更简洁。// 静态内部类单例推荐使用简洁高效 public class SingletonStaticInner { // 1. 私有构造方法 private SingletonStaticInner() {} // 2. 静态内部类里面创建实例 private static class InnerClass { private static final SingletonStaticInner INSTANCE new SingletonStaticInner(); } // 3. 全局访问入口调用时才加载静态内部类创建实例 public static SingletonStaticInner getInstance() { return InnerClass.INSTANCE; } }实战要点线程安全静态内部类加载时创建实例的过程是线程安全的类加载机制懒加载静态内部类不会随外部类加载而加载只有调用getInstance()时才加载优点写法简洁、无需手动处理同步和volatile比双重检查锁更易维护适用场景和双重检查锁类似优先选择这种写法代码更简洁。3. 单例模式的避坑要点面试必问禁止通过反射破坏单例反射可以强制调用私有构造方法创建多个实例解决方案在构造方法中添加判断若实例已存在则抛出异常禁止通过序列化破坏单例序列化会将实例写入文件反序列化时会重新创建实例解决方案重写readResolve()方法返回已有的单例实例不要滥用单例单例是“全局唯一”若后续需求需要多个实例修改成本极高只有明确需要“唯一实例”时才使用Spring中的单例Spring容器中的Bean默认是单例模式非上述写法通过容器管理但可以通过scope属性修改为多例。二、Java常用其他设计模式5种覆盖80%场景除了单例模式以下5种设计模式在Java开发中最常用同样用“类比核心作用实战代码”讲解重点掌握“适用场景”避免用错场景。1. 工厂模式Factory Pattern—— 对象创建的“流水线”核心作用封装对象的创建过程让调用者无需关心对象的创建细节只需通过工厂获取对象降低代码耦合度。类比手机工厂——我们想要一部手机不用自己去组装new对象只要告诉工厂“要华为手机”或“要苹果手机”工厂就会直接给我们做好的手机我们不用关心手机的零件、组装流程。适用场景对象创建复杂需要很多参数、步骤、需要统一管理对象创建、后续可能扩展多种对象类型比如支付方式微信支付、支付宝支付。// 1. 定义抽象产品手机 public interface Phone { void make(); // 手机制造方法 } // 2. 定义具体产品华为手机、苹果手机 public class HuaweiPhone implements Phone { Override public void make() { System.out.println(制造华为手机); } } public class Iphone implements Phone { Override public void make() { System.out.println(制造苹果手机); } } // 3. 定义工厂手机工厂 public class PhoneFactory { // 根据类型创建对应的手机对象 public static Phone getPhone(String type) { if (huawei.equals(type)) { return new HuaweiPhone(); } else if (iphone.equals(type)) { return new Iphone(); } else { throw new RuntimeException(不支持该类型的手机); } } } // 4. 测试使用 public class Test { public static void main(String[] args) { // 调用工厂获取对象无需关心创建细节 Phone huawei PhoneFactory.getPhone(huawei); huawei.make(); // 输出制造华为手机 Phone iphone PhoneFactory.getPhone(iphone); iphone.make(); // 输出制造苹果手机 } }实战要点工厂模式分为“简单工厂”上面的写法、“工厂方法”、“抽象工厂”新手先掌握简单工厂即可复杂场景再升级为工厂方法。2. 代理模式Proxy Pattern—— 对象的“代言人”核心作用为目标对象提供一个代理对象通过代理对象访问目标对象在不修改目标对象代码的前提下添加额外功能比如日志、权限校验、事务。类比房产中介——我们想买房访问目标对象不用直接找房东而是通过中介代理对象中介会帮我们筛选房源、谈价格额外功能最终我们还是和房东完成交易访问目标对象。适用场景日志记录、权限校验、事务管理、远程调用比如Spring AOP的底层就是代理模式。// 1. 定义目标对象接口房东 public interface Landlord { void rentHouse(); // 租房方法 } // 2. 实现目标对象具体房东 public class RealLandlord implements Landlord { Override public void rentHouse() { System.out.println(房东出租房子每月租金2000元); } } // 3. 定义代理对象中介 public class ProxyLandlord implements Landlord { // 持有目标对象的引用 private Landlord landlord; // 构造方法注入目标对象 public ProxyLandlord(Landlord landlord) { this.landlord landlord; } Override public void rentHouse() { // 代理对象添加额外功能前置增强 System.out.println(中介筛选租客核实身份); // 调用目标对象的方法 landlord.rentHouse(); // 代理对象添加额外功能后置增强 System.out.println(中介签订租房合同收取中介费); } } // 4. 测试使用 public class Test { public static void main(String[] args) { // 目标对象 Landlord realLandlord new RealLandlord(); // 代理对象 ProxyLandlord proxy new ProxyLandlord(realLandlord); // 通过代理对象访问目标对象 proxy.rentHouse(); } }实战要点Java中的代理分为“静态代理”上面的写法和“动态代理”JDK动态代理、CGLIB动态代理Spring AOP中主要使用动态代理新手先掌握静态代理的思想即可。3. 装饰器模式Decorator Pattern—— 给对象“穿衣服”核心作用动态地给目标对象添加额外功能不改变目标对象的结构且可以灵活组合多个功能比继承更灵活。类比给手机贴膜、戴手机壳——手机目标对象本身的功能不变但贴膜装饰器增加了“防刮”功能戴手机壳另一个装饰器增加了“防摔”功能我们可以自由组合这些装饰。适用场景需要给对象动态添加功能、功能可以灵活组合比如IO流中的BufferedReader、BufferedWriter就是装饰器模式。// 1. 定义目标对象接口手机 public interface Phone { void use(); // 使用手机 } // 2. 实现目标对象基础手机 public class BasicPhone implements Phone { Override public void use() { System.out.println(使用基础手机可打电话、发短信); } } // 3. 定义装饰器抽象类实现Phone接口持有目标对象引用 public abstract class PhoneDecorator implements Phone { protected Phone phone; public PhoneDecorator(Phone phone) { this.phone phone; } Override public void use() { phone.use(); // 调用目标对象的基础功能 } } // 4. 具体装饰器1贴膜 public class FilmDecorator extends PhoneDecorator { public FilmDecorator(Phone phone) { super(phone); } Override public void use() { super.use(); // 先调用基础功能 System.out.println(添加贴膜手机屏幕防刮); // 额外功能 } } // 5. 具体装饰器2手机壳 public class CaseDecorator extends PhoneDecorator { public CaseDecorator(Phone phone) { super(phone); } Override public void use() { super.use(); // 先调用基础功能 System.out.println(添加手机壳手机防摔); // 额外功能 } } // 6. 测试使用灵活组合装饰器 public class Test { public static void main(String[] args) { // 基础手机 Phone phone new BasicPhone(); // 给手机贴膜 phone new FilmDecorator(phone); // 给手机戴壳 phone new CaseDecorator(phone); // 使用装饰后的手机 phone.use(); } }实战要点装饰器模式和代理模式的区别——代理模式侧重“控制访问”比如权限校验装饰器模式侧重“增强功能”比如给对象添加新功能。4. 观察者模式Observer Pattern—— 事件的“订阅-通知”机制核心作用定义对象之间的“订阅-通知”关系当一个对象被观察者的状态发生变化时所有订阅它的对象观察者都会收到通知并自动更新。类比微信公众号——公众号被观察者发布新文章时所有关注公众号的用户观察者都会收到推送通知用户可以查看新文章公众号无需关心具体有多少用户关注。适用场景事件监听、消息通知、广播机制比如Spring中的事件驱动模型、MQ消息队列的底层思想。// 1. 定义观察者接口关注公众号的用户 public interface Observer { void update(String message); // 接收通知的方法 } // 2. 实现具体观察者具体用户 public class User implements Observer { private String name; public User(String name) { this.name name; } Override public void update(String message) { System.out.println(name 收到公众号通知 message); } } // 3. 定义被观察者接口公众号 public interface Observable { void addObserver(Observer observer); // 添加观察者关注 void removeObserver(Observer observer); // 移除观察者取消关注 void notifyObservers(String message); // 通知所有观察者发布文章 } // 4. 实现被观察者具体公众号 public class WeChatAccount implements Observable { // 存储所有观察者关注的用户 private ListObserver observers new ArrayList(); Override public void addObserver(Observer observer) { observers.add(observer); } Override public void removeObserver(Observer observer) { observers.remove(observer); } Override public void notifyObservers(String message) { // 遍历所有观察者发送通知 for (Observer observer : observers) { observer.update(message); } } } // 5. 测试使用 public class Test { public static void main(String[] args) { // 被观察者公众号 WeChatAccount account new WeChatAccount(); // 观察者用户 Observer user1 new User(张三); Observer user2 new User(李四); // 关注公众号 account.addObserver(user1); account.addObserver(user2); // 公众号发布文章通知所有用户 account.notifyObservers(Java设计模式精讲点击查看); } }5. 策略模式Strategy Pattern—— 算法的“灵活切换”核心作用定义一系列算法将每个算法封装起来并且使它们可以相互替换让算法的变化不影响使用算法的客户端。类比出行方式选择——我们从家到公司客户端可以选择公交、地铁、打车不同算法这些出行方式可以灵活切换不影响我们到达公司的目标且可以随时添加新的出行方式比如共享单车。适用场景有多种算法可选、算法需要灵活切换、后续可能扩展新算法比如支付方式切换、排序算法切换。// 1. 定义策略接口出行方式 public interface TravelStrategy { void travel(); // 出行方法 } // 2. 实现具体策略公交、地铁、打车 public class BusStrategy implements TravelStrategy { Override public void travel() { System.out.println(乘坐公交出行花费2元耗时40分钟); } } public class SubwayStrategy implements TravelStrategy { Override public void travel() { System.out.println(乘坐地铁出行花费3元耗时20分钟); } } public class TaxiStrategy implements TravelStrategy { Override public void travel() { System.out.println(打出租车出行花费20元耗时10分钟); } } // 3. 定义客户端出行的人 public class Person { private TravelStrategy strategy; // 设置出行策略灵活切换 public void setTravelStrategy(TravelStrategy strategy) { this.strategy strategy; } // 出行调用策略的方法 public void goToWork() { strategy.travel(); } } // 4. 测试使用灵活切换策略 public class Test { public static void main(String[] args) { Person person new Person(); // 周一坐公交 person.setTravelStrategy(new BusStrategy()); person.goToWork(); // 周二坐地铁 person.setTravelStrategy(new SubwayStrategy()); person.goToWork(); // 周三打车 person.setTravelStrategy(new TaxiStrategy()); person.goToWork(); } }三、设计模式核心总结新手必看很多新手学习设计模式的误区死记硬背写法却不知道“什么时候用”。记住设计模式的核心是“解决问题”不是“炫技”以下总结帮你避开误区单例模式唯一实例解决“频繁创建对象浪费资源”的问题重点掌握双重检查锁和静态内部类工厂模式封装对象创建解决“对象创建复杂、耦合度高”的问题代理模式控制访问增强功能解决“不修改目标对象代码添加额外功能”的问题装饰器模式动态增强功能解决“功能灵活组合”的问题比继承更灵活观察者模式订阅-通知解决“对象状态变化通知多个依赖对象”的问题策略模式算法切换解决“多种算法可选、灵活切换”的问题。补充设计模式不是“越多越好”而是“合适才好”。比如小项目中用简单的new对象比工厂模式更简洁只有当项目规模扩大、代码需要复用和维护时再引入设计模式。最后提醒面试中单例模式的线程安全问题、代理模式和装饰器模式的区别、工厂模式的应用是高频考点一定要重点掌握。实际开发中结合Spring、MyBatis等框架中的设计模式比如Spring的单例、AOP的代理能更深刻地理解设计模式的价值。如果在学习或使用设计模式时遇到问题欢迎留言讨论一起交流优化