前言

设计模式是 Java 面试里的高频专题。面试官通常不会只问“单例模式怎么写”,而是会继续追问:为什么要用这个模式,不用会有什么问题,JDK 或 Spring 里哪里用到了,业务里怎么落地,模式用多了会不会过度设计。

学习设计模式时,不建议一开始就死背 23 种模式。更好的方式是先理解它解决的问题:

1
对象怎么创建 -> 对象怎么组合 -> 对象之间怎么协作 -> 代码如何应对变化

本文按面试常见问题梳理设计模式,并给出适合练习的学习样例。

设计模式是什么

设计模式是前人在软件设计中总结出来的可复用解决方案。它不是语法,也不是框架,而是面对常见设计问题时的一种表达方式。

面试回答可以这样说:

  • 设计模式解决的是代码结构和变化管理问题。
  • 它关注职责划分、扩展性、复用性和可维护性。
  • 不能为了用模式而用模式,模式应该服务于业务复杂度。
flowchart TD
    A[业务需求变化] --> B[代码直接堆 if else]
    A --> C[抽象变化点]
    C --> D[职责拆分]
    D --> E[选择合适设计模式]
    E --> F[代码更容易扩展和维护]

常见设计原则

设计模式背后常见原则包括 SOLID 和一些通用设计思想。

原则 含义 面试回答重点
单一职责原则 一个类只负责一类职责 降低修改影响面
开闭原则 对扩展开放,对修改关闭 新增能力尽量少改老代码
里氏替换原则 子类能替换父类 继承不能破坏父类语义
接口隔离原则 接口要小而专 避免胖接口
依赖倒置原则 依赖抽象,不依赖具体实现 面向接口编程
迪米特法则 少知道原则 降低对象之间耦合
合成复用原则 优先组合而不是继承 避免继承层级膨胀

面试中常见表达:

1
设计模式不是目的,降低耦合、隔离变化、提高扩展性才是目的。

设计模式分类

经典 GoF 设计模式通常分为三类:

类型 关注点 常见模式
创建型 对象怎么创建 单例、工厂方法、抽象工厂、建造者、原型
结构型 对象怎么组合 代理、适配器、装饰器、外观、桥接、组合、享元
行为型 对象怎么协作 策略、模板方法、观察者、责任链、命令、状态、迭代器
mindmap
  root((设计模式))
    创建型
      单例
      工厂
      建造者
    结构型
      代理
      适配器
      装饰器
      外观
    行为型
      策略
      模板方法
      观察者
      责任链
      状态

单例模式

单例模式保证一个类在 JVM 中只有一个实例,并提供全局访问点。

适用场景:

  • 配置中心客户端。
  • 线程池管理器。
  • 缓存管理器。
  • 无状态工具类的统一实例。

推荐写法之一是静态内部类:

1
2
3
4
5
6
7
8
9
10
11
12
public class ConfigManager {
private ConfigManager() {
}

private static class Holder {
private static final ConfigManager INSTANCE = new ConfigManager();
}

public static ConfigManager getInstance() {
return Holder.INSTANCE;
}
}

优点:

  • 懒加载。
  • 线程安全。
  • 实现简单。

面试追问:

  • 双重检查锁为什么要加 volatile:防止指令重排序导致其他线程拿到未初始化完成的对象。
  • 枚举单例有什么优点:天然防反射和反序列化破坏。
  • Spring Bean 默认是不是单例:默认是单例,但它是容器级单例,不一定等同于 JVM 全局单例。

工厂模式

工厂模式用于封装对象创建逻辑,让调用方不直接依赖具体类。

学习样例:根据支付类型创建不同支付处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface PayHandler {
void pay(String orderNo);
}

public class AlipayHandler implements PayHandler {
public void pay(String orderNo) {
System.out.println("支付宝支付:" + orderNo);
}
}

public class WeChatPayHandler implements PayHandler {
public void pay(String orderNo) {
System.out.println("微信支付:" + orderNo);
}
}

public class PayHandlerFactory {
public static PayHandler create(String type) {
if ("ALIPAY".equals(type)) {
return new AlipayHandler();
}
if ("WECHAT".equals(type)) {
return new WeChatPayHandler();
}
throw new IllegalArgumentException("unsupported pay type");
}
}

简单工厂的缺点是新增类型时仍然要修改工厂类。复杂系统中可以结合 Spring 容器,用 Map 自动注册处理器,减少大段 if else

建造者模式

建造者模式适合创建参数很多、构造过程复杂的对象。

学习样例:构建订单查询条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class OrderQuery {
private String userId;
private String status;
private Integer pageNo;
private Integer pageSize;

private OrderQuery() {
}

public static Builder builder() {
return new Builder();
}

public static class Builder {
private final OrderQuery query = new OrderQuery();

public Builder userId(String userId) {
query.userId = userId;
return this;
}

public Builder status(String status) {
query.status = status;
return this;
}

public Builder page(int pageNo, int pageSize) {
query.pageNo = pageNo;
query.pageSize = pageSize;
return this;
}

public OrderQuery build() {
return query;
}
}
}

调用方式:

1
2
3
4
5
OrderQuery query = OrderQuery.builder()
.userId("1001")
.status("PAID")
.page(1, 20)
.build();

面试回答重点:建造者模式让复杂对象创建更清晰,避免构造方法参数过多,也便于设置默认值和做参数校验。

原型模式

原型模式通过复制已有对象创建新对象,适合对象创建成本高或需要保留模板配置的场景。

常见例子:

  • 复制报表模板。
  • 复制规则配置。
  • 游戏对象克隆。
  • Java 中的 Object#clone

注意深拷贝和浅拷贝区别:

  • 浅拷贝只复制对象本身,引用字段仍指向原对象。
  • 深拷贝会复制引用字段指向的对象。

代理模式

代理模式是在不修改目标对象的前提下,为目标对象增加额外能力。

常见场景:

  • 日志记录。
  • 权限校验。
  • 事务控制。
  • RPC 调用。
  • Spring AOP。

学习样例:给订单服务加日志代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface OrderService {
void createOrder(String orderNo);
}

public class OrderServiceImpl implements OrderService {
public void createOrder(String orderNo) {
System.out.println("创建订单:" + orderNo);
}
}

public class OrderServiceProxy implements OrderService {
private final OrderService target;

public OrderServiceProxy(OrderService target) {
this.target = target;
}

public void createOrder(String orderNo) {
System.out.println("开始记录日志");
target.createOrder(orderNo);
System.out.println("结束记录日志");
}
}

面试中要区分:

  • 静态代理:手写代理类。
  • JDK 动态代理:基于接口。
  • CGLIB 代理:基于继承生成子类。

适配器模式

适配器模式用于把不兼容的接口转换成调用方期望的接口。

学习样例:系统统一调用短信接口,但不同供应商 API 不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface SmsSender {
void send(String phone, String content);
}

public class OldSmsClient {
public void sendMessage(String mobile, String text) {
System.out.println("old sms send");
}
}

public class OldSmsAdapter implements SmsSender {
private final OldSmsClient client = new OldSmsClient();

public void send(String phone, String content) {
client.sendMessage(phone, content);
}
}

适配器关注“接口转换”,不是增强功能。增强功能通常更像装饰器或代理。

装饰器模式

装饰器模式用于在运行时动态增强对象能力,且增强后的对象仍然保持原接口。

Java IO 是典型例子:

1
InputStream input = new BufferedInputStream(new FileInputStream("a.txt"));

学习样例:给通知能力动态增加短信、邮件、企业微信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface Notifier {
void notify(String message);
}

public class BaseNotifier implements Notifier {
public void notify(String message) {
System.out.println("站内信:" + message);
}
}

public class SmsNotifierDecorator implements Notifier {
private final Notifier delegate;

public SmsNotifierDecorator(Notifier delegate) {
this.delegate = delegate;
}

public void notify(String message) {
delegate.notify(message);
System.out.println("短信:" + message);
}
}

装饰器和代理的区别:

  • 代理更关注控制访问,比如权限、事务、远程调用。
  • 装饰器更关注增强能力,并且可以多层叠加。

外观模式

外观模式为复杂子系统提供一个简单入口。

学习样例:下单接口背后要调用库存、优惠券、支付、通知等多个服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class OrderFacade {
private final StockService stockService;
private final CouponService couponService;
private final PayService payService;

public OrderFacade(StockService stockService, CouponService couponService, PayService payService) {
this.stockService = stockService;
this.couponService = couponService;
this.payService = payService;
}

public void submitOrder(String orderNo) {
stockService.lockStock(orderNo);
couponService.useCoupon(orderNo);
payService.pay(orderNo);
}
}

外观模式能降低调用方复杂度,但不能把所有逻辑都堆进 Facade,否则会变成新的大泥球。

策略模式

策略模式把一组可替换算法封装起来,调用方通过抽象接口使用它们。

学习样例:不同优惠券计算规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface DiscountStrategy {
int discount(int amount);
}

public class FullReductionStrategy implements DiscountStrategy {
public int discount(int amount) {
return amount >= 100 ? amount - 20 : amount;
}
}

public class PercentDiscountStrategy implements DiscountStrategy {
public int discount(int amount) {
return amount * 90 / 100;
}
}

结合 Spring 可以这样管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Service("FULL_REDUCTION")
public class FullReductionStrategy implements DiscountStrategy {
public int discount(int amount) {
return amount >= 100 ? amount - 20 : amount;
}
}

@Service("PERCENT")
public class PercentDiscountStrategy implements DiscountStrategy {
public int discount(int amount) {
return amount * 90 / 100;
}
}

@Service
public class DiscountService {
private final Map<String, DiscountStrategy> strategies;

public DiscountService(Map<String, DiscountStrategy> strategies) {
this.strategies = strategies;
}

public int calculate(String type, int amount) {
return strategies.get(type).discount(amount);
}
}

面试回答重点:策略模式可以消除大量算法分支,但如果策略很少且变化不频繁,简单 if else 也可以接受。

模板方法模式

模板方法模式把固定流程放在父类,把变化步骤交给子类。

学习样例:导入文件流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class ImportTemplate {
public final void importFile(String file) {
validate(file);
Object data = parse(file);
save(data);
afterImport(data);
}

protected void validate(String file) {
System.out.println("公共校验");
}

protected abstract Object parse(String file);

protected abstract void save(Object data);

protected void afterImport(Object data) {
}
}

适合场景:

  • 流程固定,局部步骤变化。
  • 父类控制流程顺序。
  • 子类只实现差异点。

Spring 中的 JdbcTemplateRestTemplate 命名上就体现了模板思想。

观察者模式

观察者模式用于对象状态变化后通知多个观察者。

学习样例:订单支付成功后触发积分、消息、发货。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface OrderPaidListener {
void onPaid(String orderNo);
}

public class PointListener implements OrderPaidListener {
public void onPaid(String orderNo) {
System.out.println("增加积分");
}
}

public class MessageListener implements OrderPaidListener {
public void onPaid(String orderNo) {
System.out.println("发送通知");
}
}

public class OrderEventPublisher {
private final List<OrderPaidListener> listeners;

public OrderEventPublisher(List<OrderPaidListener> listeners) {
this.listeners = listeners;
}

public void publishPaid(String orderNo) {
for (OrderPaidListener listener : listeners) {
listener.onPaid(orderNo);
}
}
}

Spring 事件机制、消息队列发布订阅,都可以看作观察者思想的工程化落地。

责任链模式

责任链模式把多个处理器串起来,每个处理器负责一段逻辑。

学习样例:订单提交前的校验链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface OrderCheckHandler {
void check(OrderContext context);
}

public class StockCheckHandler implements OrderCheckHandler {
public void check(OrderContext context) {
System.out.println("校验库存");
}
}

public class RiskCheckHandler implements OrderCheckHandler {
public void check(OrderContext context) {
System.out.println("风控校验");
}
}

public class OrderCheckChain {
private final List<OrderCheckHandler> handlers;

public OrderCheckChain(List<OrderCheckHandler> handlers) {
this.handlers = handlers;
}

public void check(OrderContext context) {
for (OrderCheckHandler handler : handlers) {
handler.check(context);
}
}
}

常见应用:

  • Servlet Filter。
  • Spring Security 过滤器链。
  • Netty Pipeline。
  • 参数校验链。
  • 审批流。

状态模式

状态模式把不同状态下的行为封装到不同状态类中,避免大量状态判断。

学习样例:订单状态流转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface OrderState {
void pay(OrderContext context);
void cancel(OrderContext context);
}

public class CreatedState implements OrderState {
public void pay(OrderContext context) {
context.setState(new PaidState());
}

public void cancel(OrderContext context) {
context.setState(new CanceledState());
}
}

public class PaidState implements OrderState {
public void pay(OrderContext context) {
throw new IllegalStateException("订单已支付");
}

public void cancel(OrderContext context) {
throw new IllegalStateException("已支付订单不能直接取消");
}
}

状态模式适合状态多、状态行为差异明显、状态流转规则复杂的场景。简单状态机也可以用枚举加规则表实现。

命令模式

命令模式把请求封装成对象,适合排队、撤销、重试、日志记录等场景。

学习样例:后台任务命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Command {
void execute();
}

public class SendEmailCommand implements Command {
public void execute() {
System.out.println("发送邮件");
}
}

public class CommandExecutor {
public void submit(Command command) {
command.execute();
}
}

常见应用:

  • 任务队列。
  • MQ 消息处理。
  • GUI 操作撤销。
  • 批处理任务。

设计模式在 Spring 中的应用

Spring 是设计模式的集中应用场景。

Spring 场景 对应模式 说明
BeanFactory、FactoryBean 工厂模式 封装对象创建
ApplicationContext 工厂、外观 统一管理 Bean 和容器能力
AOP 代理模式 增强目标方法
JdbcTemplate、TransactionTemplate 模板方法 固定流程,开放回调
ApplicationEvent 观察者模式 事件发布和监听
HandlerInterceptor、Filter 责任链模式 请求处理链
BeanPostProcessor 扩展点、责任链思想 Bean 生命周期增强
Strategy Bean Map 策略模式 多实现按类型选择

面试中如果能结合 Spring 回答,会比单纯背定义更有说服力。

业务场景怎么选设计模式

业务问题 推荐思路 可能模式
支付方式很多 抽象支付处理器 策略、工厂
订单校验步骤很多 拆成多个校验节点 责任链
导入流程固定但文件类型不同 固定流程,变化步骤下沉 模板方法
第三方接口不统一 做统一接口转换 适配器
方法前后要加日志、权限、事务 不改业务类增加增强 代理
通知方式可叠加 动态增强通知能力 装饰器
订单状态流转复杂 状态行为内聚 状态模式
创建复杂查询对象 链式构建 建造者
flowchart TD
    A[遇到复杂业务逻辑] --> B{变化点是什么}
    B -->|算法变化| C[策略模式]
    B -->|流程固定步骤变化| D[模板方法]
    B -->|处理步骤可插拔| E[责任链]
    B -->|接口不兼容| F[适配器]
    B -->|需要增强方法| G[代理或装饰器]
    B -->|状态行为变化| H[状态模式]

学习样例建议

学习设计模式最有效的方式是用一个小业务反复重构。推荐使用“订单系统”作为练习样例,因为它天然包含多种变化点。

可以按以下步骤练习:

  1. 先写一个简单下单服务,把所有逻辑堆在一个方法里。
  2. 把不同优惠计算拆成策略模式。
  3. 把支付方式创建拆成工厂模式。
  4. 把订单校验拆成责任链模式。
  5. 把导入订单流程拆成模板方法模式。
  6. 把支付成功后的积分、通知、发货拆成观察者模式。
  7. 把订单状态流转拆成状态模式。
  8. 用代理模式给关键方法加日志或耗时统计。

练习主线可以这样设计:

1
提交订单 -> 校验库存/风控/优惠券 -> 计算优惠 -> 创建支付处理器 -> 支付成功 -> 发布事件 -> 状态流转

对应模式:

练习模块 设计模式
优惠计算 策略模式
支付处理器创建 工厂模式
订单校验 责任链模式
导入订单 模板方法模式
支付成功后通知 观察者模式
订单状态流转 状态模式
日志和耗时统计 代理模式
通知渠道叠加 装饰器模式

高频面试题

设计模式有什么用?

设计模式用于解决常见设计问题,本质是隔离变化、降低耦合、提高复用和扩展能力。不能为了模式而模式,要看业务复杂度是否值得引入。

策略模式和工厂模式有什么区别?

工厂模式关注对象怎么创建,策略模式关注算法或行为怎么替换。实际项目中经常组合使用:工厂负责拿到策略对象,策略负责执行业务逻辑。

代理模式和装饰器模式有什么区别?

代理模式更关注控制访问,比如权限、事务、远程调用;装饰器模式更关注动态增强对象能力,而且可以多层叠加。

模板方法和策略模式有什么区别?

模板方法通过继承固定流程,子类重写变化步骤;策略模式通过组合替换算法。模板方法强调流程骨架,策略模式强调行为替换。

责任链模式有什么问题?

链路过长会增加排查成本;处理器顺序不清晰会引入隐性依赖;如果没有统一异常和中断机制,容易出现部分处理成功、部分失败的问题。

单例模式怎么保证线程安全?

可以使用枚举、静态内部类、双重检查锁等方式。双重检查锁里的实例变量必须加 volatile,避免指令重排序导致拿到未初始化完成的对象。

Spring 中用到了哪些设计模式?

BeanFactory 用到工厂模式,AOP 用到代理模式,JdbcTemplate 用到模板方法,ApplicationEvent 用到观察者模式,Filter 和 Interceptor 体现责任链思想。

怎么判断是不是过度设计?

如果业务变化点不明确、实现类只有一个、抽象层没有带来复用或扩展收益,只是增加理解成本,就可能是过度设计。设计模式应该在变化真实存在时使用。

总结

设计模式面试不要只背定义,可以围绕四条主线回答:

  1. 创建型模式解决对象创建问题:单例、工厂、建造者、原型。
  2. 结构型模式解决对象组合问题:代理、适配器、装饰器、外观。
  3. 行为型模式解决对象协作问题:策略、模板方法、观察者、责任链、状态。
  4. 工程落地要结合 Spring、订单、支付、导入、消息通知等真实场景。

真正掌握设计模式的标志,不是能说出 23 种模式名字,而是能识别业务变化点,并用合适的抽象让代码更容易扩展和维护。