# 谈谈你了解的最常见的几种设计模式,说说他们的应用场景

8 min read
Table of Contents

谈谈你了解的最常见的几种设计模式,说说他们的应用场景

【核心定义】
设计模式是针对软件设计中反复出现的特定问题,所提供的通用、可复用的解决方案模板,它代表了行业最佳实践。

【关键要点】

  1. 单例模式:确保一个类只有一个实例,并提供全局访问点。

    • 原理:通过私有化构造函数、静态方法提供实例,并控制实例化次数。
    • 应用:配置管理、线程池、数据库连接池等需要全局唯一对象的场景。
  2. 工厂模式(含简单工厂、工厂方法、抽象工厂):将对象创建逻辑封装,降低客户端与具体类的耦合。

    • 原理:通过接口或抽象类定义创建方法,由子类决定实例化哪个类。
    • 应用:JDK 中的 Calendar.getInstance()、Spring 的 BeanFactory、日志框架中 LoggerFactory。
  3. 观察者模式:定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会自动收到通知并更新。

    • 原理:主题(Subject)维护观察者(Observer)列表,通过 notify() 方法遍历调用观察者的更新方法。
    • 应用:事件监听机制(如 Java GUI 的 ActionListener)、消息队列、React/Vue 的数据响应式系统。
  4. 策略模式:定义一系列算法,将每个算法封装起来,并使它们可以互相替换。

    • 原理:通过接口定义算法族,具体策略类实现接口,上下文类持有策略接口引用。
    • 应用:排序算法切换、支付方式选择(微信/支付宝/银行卡)、Spring 的 Resource 资源加载策略。
  5. 代理模式:为其他对象提供一种代理以控制对这个对象的访问。

    • 原理:代理类与真实类实现同一接口,代理类在调用真实对象前后可添加额外逻辑。
    • 应用:Spring AOP 的动态代理、远程代理(RPC)、保护代理(权限控制)。

【深度推导/细节】

  • 单例模式的双重检查锁定(DCL):
    Step 1: 第一次检查实例是否为 null(避免每次进入同步块)。
    Step 2: 同步代码块,第二次检查实例是否为 null(防止多线程同时通过第一次检查后重复创建)。
    Step 3: 使用 volatile 修饰实例,禁止指令重排序,确保其他线程看到完全初始化的对象。

    • 痛点:早期 DCL 在 Java 1.4 及之前因内存模型问题可能失效,Java 5 后 volatile 修复此问题。
  • 工厂模式 vs. 抽象工厂:

    • 工厂方法:针对一个产品等级结构(如“汽车”),一个工厂创建一种产品。
    • 抽象工厂:针对多个产品等级结构(如“汽车 + 轮胎”),一个工厂创建一族相关产品。
    • 关键区别:抽象工厂强调产品族,工厂方法强调产品扩展。

【关联/对比】

  • 代理模式 vs. 装饰器模式:

    • 代理模式:控制访问,重点在于增强对象访问的逻辑(如权限、延迟加载)。
    • 装饰器模式:动态添加功能,重点在于增强对象本身的行为(如 Java I/O 流的 BufferedInputStream)。
    • 结构相似,但意图不同:代理是控制,装饰是增强。
  • 观察者模式 vs. 发布-订阅模式:

    • 观察者:主题直接维护观察者列表,耦合较高。
    • 发布-订阅:通过消息队列/总线解耦,发布者和订阅者互不知晓。

『面试官追问』

  1. 单例模式在分布式环境下如何保证唯一性?

    • 答:需借助外部机制,如分布式锁(Redis/ZooKeeper)或全局唯一 ID 服务(Snowflake),将单例升级为“集群单例”。
  2. Spring 中用了哪些设计模式?

    • 答:工厂模式(BeanFactory)、代理模式(AOP)、单例模式(默认 Bean 作用域)、模板方法模式(JdbcTemplate)、观察者模式(ApplicationEvent)。
  3. 策略模式与状态模式的区别?

    • 答:策略模式是客户端主动选择算法,状态模式是对象内部状态自动切换行为(如订单状态从“待支付”到“已发货”触发不同处理)。
  4. 如何避免过度设计模式?

    • 答:遵循 KISS 原则,在代码出现重复、变化或复杂度提升时再引入模式,而非预先套用。

【直击痛点】

  • 单例的 volatile 与 DCL:
    • 为什么需要 volatile?因为 new Singleton() 非原子操作(分配内存、初始化、引用赋值),可能发生重排序导致其他线程拿到未完全初始化的实例。volatile 禁止重排序,保证可见性。
  • 工厂模式的选择依据:
    • 简单工厂:产品类型固定且少。
    • 工厂方法:产品类型易扩展,符合开闭原则。
    • 抽象工厂:需要创建产品族,且产品族可能变化。

【逻辑复现】

  • 观察者模式的通知流程(以 Java 自定义实现为例):
    Step 1: 主题(如 WeatherStation)维护一个 List<Observer>。
    Step 2: 观察者(如 PhoneDisplay)实现 Observer 接口的 update() 方法。
    Step 3: 主题状态改变时,调用 notifyObservers() 遍历列表,执行每个观察者的 update()。
    Step 4: 观察者从主题拉取或接收推送的新数据,更新自身显示。

【版本差异】

  • Java 中的单例演进:

    • Java 1.4 前:DCL 需额外配合 synchronized 与类锁保证安全。
    • Java 5 后:volatile 语义增强,DCL 成为标准写法。
    • Java 8+:推荐使用枚举实现单例(如 public enum Singleton { INSTANCE; }),简洁且防反射攻击。
  • Spring 框架中的代理模式:

    • Spring AOP 默认使用 JDK 动态代理(基于接口),若目标类无接口则切换为 CGLIB 字节码增强。Spring 4+ 后可通过配置强制使用 CGLIB。
My avatar

文章看完了,不打算去别的篇章转转吗?👀 感谢你的耐心阅读!如果你有问题想探讨,或者发现了文中的 Bug,请务必通过页脚的链接联系我


More Posts

# 说一下 Kafka 中关于事务消息的实现?

8 min read

Kafka 事务消息是一种跨分区、跨会话的原子性写入机制,它通过事务协调器(Transaction Coordinator) 和事务日志(Transaction Log) 来保证生产者发送的多个消息要么全部成功提交,要么全部失败回滚,从而实现类似数据库的 ACID 特性中的原子性。

Read

Comments