设计模式面试
设计模式面试
综合
【简单】什么是设计模式?为什么需要设计模式?
设计模式是软件设计中常见问题的典型解决方案。
设计模式是针对软件设计中常见问题的、可重用的解决方案模板和最佳实践。
模式是针对软件设计中常见问题的解决方案工具箱, 它们定义了一种让你的团队能更高效沟通的通用语言。
【中等】设计模式可以分为哪几类?一共有多少种主流的设计模式?

一共有 23 种主流设计模式
- 创建型模式:创建型模式提供了创建对象的机制, 能够提升已有代码的灵活性和可复用性。
- 结构型模式:结构型模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
- 行为型模式:行为模式负责对象间的高效沟通和职责委派。
创建型模式
【中等】单例模式有哪几种实现?如何保证线程安全?
线程不安全:如果两个线程同时执行到 if (instance == null)
判断,且都发现为 null,则会创建两个实例
public class Singleton {
private static Singleton instance;
// 私有构造函数,防止外部 new
private Singleton() {}
// 全局访问点
public static Singleton getInstance() {
if (instance == null) { // 线程不安全的发生点
instance = new Singleton();
}
return instance;
}
}
public class Singleton {
// 类加载时就直接初始化
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
如果没有 volatile
,JVM 可能会进行指令重排序,将步骤 3 和步骤 2 调换顺序。这可能导致其他线程拿到一个未完全初始化的对象。volatile
可以防止这种重排序,保证可见性和有序性。
线程安全,延迟加载,效率较高(只在第一次初始化时同步)。
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;
}
}
- 原理:JVM 在加载外部类时,不会立即加载其内部类。内部类
SingletonHolder
只有在getInstance()
方法被调用时才会被加载和初始化,从而初始化INSTANCE
实例。 - 优点:
- 线程安全:由 JVM 类加载机制保证。
- 懒加载:只有在调用
getInstance()
时才会实例化。 - 高效:无需同步锁,性能高。
public class Singleton {
private Singleton() {}
// 静态内部类
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
@枚举
这是《Effective Java》强烈推荐的方式,如果需要抵抗序列化和反射攻击,这是最好的选择。
public enum Singleton {
INSTANCE; // 唯一的实例
// 可以添加任意方法
public void doSomething() {
System.out.println("Doing something...");
}
}
使用方法:Singleton.INSTANCE.doSomething();
优点:
- 极其简单:代码简洁。
- 线程安全:枚举实例的创建由 JVM 保证全局唯一。
- 防止反射攻击:枚举类默认不允许通过反射创建实例,能有效防止通过反射破坏单例。
- 防止反序列化破坏:Java 规范保证每个枚举类型和定义的枚举变量都是唯一的,在序列化和反序列化时不会创建新的对象。
【中等】什么是简单工厂模式?有哪些经典的应用场景?
简单工厂模式由一个工厂类统一负责对象的创建,根据传入的参数决定具体创建哪种产品。 客户端无需关心创建细节,实现创建与使用的分离。
三大角色
- 工厂 (Factory):核心,包含创建逻辑的静态方法。
- 抽象产品 (Product):所有具体产品的父类或接口,定义公共方法。
- 具体产品 (Concrete Product):工厂创建的目标,实现抽象产品接口。
经典应用场景
- JDK:
Calendar.getInstance()
,NumberFormat.getInstance()
- 日志框架:
LoggerFactory.getLogger()
- 数据库连接:
DriverManager.getConnection()
, 连接池获取连接 - 加密解密:
KeyGenerator.getInstance("AES")
- GUI 工具包:跨平台创建控件
优缺点
- 优点:解耦(创建与使用分离)、职责清晰(代码易于维护)。
- 缺点:违反开闭原则(新增产品必须修改工厂逻辑),工厂类会变得臃肿。
【中等】什么是工厂模式?有哪些经典的应用场景?
工厂模式将对象的创建过程封装起来,与使用它的代码解耦。客户端不关心对象如何被创建,只关心如何使用。
三种工厂模式对比
模式 | 核心思想 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
简单工厂 | 一个“万能”工厂,根据参数创建一种类型的不同产品。 | 结构简单,封装创建过程。 | 违反开闭原则,新增产品需修改工厂。 | 产品类型少且稳定(如计算器运算)。 |
工厂方法 | 定义一个创建接口,让子类工厂决定实例化哪一种产品。 | 符合开闭原则,易于扩展新产品。 | 类数量增多(每产品一工厂)。 | 需要频繁扩展新产品个体(如新增日志输出源)。 |
抽象工厂 | 一个工厂创建一整套(一族) 相关的产品。 | 保证产品族兼容性,切换整套产品方便。 | 难以支持新增产品种类。 | 需要创建一组有关联的产品(如整套跨平台 UI 控件)。 |
经典应用场景
JDK/框架级应用:
Calendar.getInstance()
,DriverManager.getConnection()
(简单工厂)Collection.iterator()
(工厂方法 - 每个集合生产自己的迭代器)- Spring IOC 容器:终极的工厂,
getBean()
是核心工厂方法。
工具库:
- 日志框架(SLF4J):
getLogger()
是工厂方法,绑定不同实现(Logback, Log4j)就是具体工厂。 - JDBC:
Connection
是抽象工厂,其createStatement()
是工厂方法。
- 日志框架(SLF4J):
业务系统:
- 支付系统:
PaymentFactory
下有AliPayFactory
,WeChatPayFactory
(工厂方法),每个工厂生产一套支付产品(支付、退款、查询)。 - UI 主题/游戏风格:
DarkThemeFactory
和LightThemeFactory
分别生产一套 dark/light 风格的按钮、对话框、菜单(抽象工厂)。
- 支付系统:
一句话总结
- 简单工厂:怎么造?我一个厂子全包了!(产品扩展麻烦)
- 工厂方法:造什么?我开分厂,每个分厂专精一样!(个体扩展方便)
- 抽象工厂:要配套!我开集团,每个集团生产一套组合产品!(整套切换方便)
结构型模式
【中等】什么是代理模式?有哪些经典的应用场景?
代理模式通过一个“中介”(代理对象)来控制和管理对另一个对象(真实对象)的访问。代理与真实对象实现相同的接口,使客户端无感知。
典型应用场景
代理类型 | 核心目的 | 经典应用场景 |
---|---|---|
RPC | 隐藏对象不在本地的事实,提供本地代表。 | RPC 框架(如 gRPC, Dubbo)、Java RMI。客户端调用的 Stub 就是代理。 |
AOP | 在访问前后添加额外操作,增强功能。 | Spring AOP(日志、事务)、ORM 懒加载(Hibernate/MyBatis)、缓存。 |
代理模式的核心价值在于:在不修改原始对象的情况下,通过代理间接访问,从而实现权限控制、功能增强或性能优化。它是 Spring AOP 等技术的基石。
行为型模式
【中等】什么是策略模式?有哪些经典的应用场景?
策略模式定义一族算法,封装每个算法,并使它们可以互相替换。策略模式让算法的变化独立于使用它的客户端,实现“做什么”和“怎么做”的分离。
三大核心角色
- 上下文 (Context):持有策略的引用,负责对接客户端,并委托策略执行。
- 抽象策略 (Strategy):策略家族的抽象(接口或抽象类),定义了所有策略必须实现的方法。
- 具体策略 (Concrete Strategy):实现了抽象策略接口的具体算法。
核心价值与优点
- 消除条件判断:用组合替代大量
if-else
或switch
,使代码更清晰。 - 符合开闭原则:新增策略只需添加新类,无需修改上下文或已有代码,扩展性极佳。
- 算法复用与独立:算法可以独立变化和复用。
经典应用场景
- 电商促销:无缝切换折扣、立减、满减等不同计价策略。
- 支付系统:灵活支持支付宝、微信、银联等不同支付方式。
- 排序比较:Java 中的
Comparator
接口是典范,传入不同比较器实现不同排序规则。 - 导航规划:根据用户选择(最短时间、最短距离、避开收费)计算不同路线。
- 游戏 AI:角色动态切换攻击(近战、远程)或移动(行走、奔跑)策略。
当代码中出现大量条件判断来选择不同行为时,就是使用策略模式的最佳时机。它通过“组合”和“委托”将算法灵活地包装起来,便于任意替换和扩展。
【中等】什么是观察者模式?有哪些经典的应用场景?
观察者模式实现一对多的依赖关系(又称发布-订阅模型),当一个对象(主题)状态改变时,它能自动通知并更新所有依赖它的对象(观察者)。
两大核心角色
- 主题 (Subject):维护一个观察者列表,提供注册、移除和通知的方法。
- 观察者 (Observer):定义一个接收通知的更新接口。
核心价值与优点
- 松耦合:主题与观察者互不知晓对方细节,仅通过接口交互,独立性极强。
- 动态联动:支持在运行时动态地添加或删除观察者,非常灵活。
- 广播通信:主题的一次调用可以触发所有观察者的更新。
经典应用场景
- GUI 事件处理:按钮、鼠标等控件(主题)的事件监听器(观察者)。
- 发布-订阅消息队列:Kafka、RabbitMQ 等中间件(分布式观察者模式)。
- 前端响应式框架:Vue/React 的响应式系统(数据变 → 视图自动更新)。
- MVC 架构:模型(Model)数据变化 → 自动通知视图(View)更新。
- 社交媒体推送:你关注的人(主题)发帖 → 你(观察者)的时间线更新。
【中等】什么是模板方法模式?有哪些经典的应用场景?
模板方法模式中,父类定义算法骨架(模板方法),子类实现具体步骤。实现“流程固定,细节可变”,确保逻辑顺序一致的同时允许特定步骤个性化。
两大核心角色
- 抽象类 (Abstract Class):
- 包含一个
final
模板方法(定义不可更改的算法流程)。 - 包含抽象方法(必须由子类实现)、具体方法(通用步骤)和钩子方法(可选步骤,提供额外扩展点)。
- 包含一个
- 具体子类 (Concrete Class):仅负责实现父类定义的抽象方法,填充算法骨架中的具体步骤。
核心价值与优点
- 代码复用:将公共流程提升至父类,避免子类代码重复。
- 反向控制(好莱坞原则):父类控制流程,“调用”子类,而非子类调用父类。
- 便于扩展与维护:新增行为只需增加子类;修改流程只需改动父类一处。
经典应用场景
- 框架设计:是框架的基石,定义扩展流程。
- Java Servlet:
HttpServlet.service()
是模板方法,调用开发者重写的doGet()/doPost()
。 - Spring JdbcTemplate:固定了数据库操作流程(取连接、执行、释放资源),开发者提供 SQL 和结果处理逻辑。
- Java Servlet:
- 单元测试:JUnit 的
TestCase
定义了setUp() → testMethod() → tearDown()
的标准流程。 - 生命周期管理:Android Activity 的生命周期方法调用顺序由系统框架固定。
- 通用算法:Java 集合框架的
AbstractList
提供了基于get()
和size()
的通用算法实现。
【中等】什么是责任链模式?有哪些经典的应用场景?
责任链模式将处理请求的对象连成一条链,请求沿链传递,直到有一个对象处理它为止。实现“谁有空谁处理”的“踢皮球”机制。
两大核心角色
- 处理者 (Handler):定义处理请求的接口,并持有下一个处理者的引用。
- 具体处理者 (Concrete Handler):实现处理接口。能处理则处理,不能处理则转发给下一个。
核心价值与优点
- 解耦:请求发送者无需知道谁处理请求,只需向链首提交。
- 动态灵活:可动态增删或调整处理者顺序,灵活改变处理流程。
- 职责清晰:每个处理者只需关注自己的职责范围。
经典应用场景
- 审批流程:多级审批(如报销、请假),根据金额、类型等条件由不同级别领导处理。
- Web 过滤器/拦截器:Java Servlet FilterChain 和 Spring Interceptor,请求依次通过权限校验、日志、编码等过滤器。
- 异常处理:Java 的
try-catch
块就是责任链,沿调用栈寻找匹配的异常处理器。 - 事件冒泡:UI 编程中(如浏览器 DOM),事件从子元素向父元素逐级传播处理。
- 日志系统:日志消息根据级别(DEBUG, ERROR)被传递到不同的输出端(控制台、文件)。