总结与问答
设计模式全景图
经过前面四篇的学习,我们已经逐一拆解了创建型、结构型、行为型三大类设计模式。现在是时候站到更高的视角,把所有 23 种模式串联起来,看看它们在真实项目中的落地位置。
先用一张思维导图回顾全貌:
一句话帮你记住三大类的核心:
| 分类 | 核心问题 | 记忆关键词 |
|---|---|---|
| 创建型 | 怎样创建对象? | 造什么、怎么造 |
| 结构型 | 怎样组合对象? | 拼积木、套壳 |
| 行为型 | 对象怎样协作? | 谁干活、怎么配合 |
一、Spring 框架中用到的设计模式
Spring 可以说是设计模式的博物馆——几乎每种常见模式都能在 Spring 源码中找到身影。下面逐一对应到 Spring 中的具体位置。
1. 工厂模式 —— IoC 容器
Spring 的核心就是一个巨大的工厂——IoC 容器。
BeanFactory:最基础的工厂接口,定义了getBean()方法ApplicationContext:BeanFactory的增强版,额外提供了事件发布、国际化、资源加载等能力FactoryBean:特殊的 Bean,实现此接口后可以自定义某个 Bean 的创建逻辑
// 你不需要自己 new 对象,Spring 这个"工厂"帮你创建和管理
@Autowired
private UserService userService;
// FactoryBean 的用法:自定义复杂对象的创建
public class MyFactoryBean implements FactoryBean<ComplexService> {
public ComplexService getObject() {
return new ComplexService(/* 复杂的构建过程 */);
}
public Class<?> getObjectType() { return ComplexService.class; }
}2. 单例模式 —— Bean 的默认作用域
Spring 容器中的 Bean 默认就是单例的(scope=singleton)。整个应用中,同一类型的 Bean 只有一个实例,所有注入点拿到的都是同一个对象。
Spring 的单例实现和传统的"私有构造器"方式不同——它是通过容器管理实现的:容器只创建一次,并缓存在 singletonObjects 这个 ConcurrentHashMap 中。
3. 代理模式 —— Spring AOP
Spring AOP 的底层就是代理模式。当你给一个方法加上注解时,Spring 会为这个 Bean 创建代理对象:
- 目标类实现了接口 → 默认用 JDK 动态代理(基于接口)
- 目标类没有接口 → 自动切换到 CGLIB 代理(基于继承)
// 你写的代码
@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
// 业务逻辑
}
// Spring 在运行时做的事(伪代码)
public void transferMoney_proxy(Account from, Account to, BigDecimal amount) {
beginTransaction(); // 代理增加:开启事务
try {
target.transferMoney(from, to, amount); // 调用真实方法
commitTransaction(); // 代理增加:提交事务
} catch (Exception e) {
rollbackTransaction(); // 代理增加:回滚事务
throw e;
}
}@Cacheable、@Async、@Retryable 等注解的实现原理都是类似的。
4. 模板方法模式 —— 各种 Template 类
Spring 中带 "Template" 后缀的类几乎都用了模板方法模式:
| 类名 | 封装的固定流程 | 你需要提供的可变部分 |
|---|---|---|
JdbcTemplate | 获取连接 → 执行 → 处理结果 → 关闭 | SQL 语句和 RowMapper |
RestTemplate | 构建请求 → 发送 → 解析响应 | URL 和返回类型 |
TransactionTemplate | 开启事务 → 执行 → 提交/回滚 | 业务逻辑 |
RedisTemplate | 获取连接 → 执行命令 → 释放连接 | Redis 操作 |
此外,AbstractApplicationContext.refresh() 方法定义了 Spring 容器启动的 12 个步骤骨架,每个步骤是可以被子类覆写的模板方法。
5. 观察者模式 —— Spring 事件机制
Spring 的事件机制是对观察者模式的完美封装:
// 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
private Order order;
public OrderCreatedEvent(Object source, Order order) {
super(source);
this.order = order;
}
}
// 发布事件(被观察者角色)
@Autowired
private ApplicationEventPublisher publisher;
public void createOrder(Order order) {
// ...创建订单
publisher.publishEvent(new OrderCreatedEvent(this, order));
}
// 监听事件(观察者角色)——可以有多个监听者
@EventListener
public void sendSMS(OrderCreatedEvent event) {
System.out.println("发短信通知: " + event.getOrder().getId());
}
@EventListener
public void addPoints(OrderCreatedEvent event) {
System.out.println("赠送积分: " + event.getOrder().getId());
}每新增一个"订单创建后要做的事",只需要加一个 @EventListener 方法,完全不需要改创建订单的代码。
6. 策略模式 —— 多实现自动注入
Spring 天然支持将一个接口的所有实现注入到一个 Map 中,这是实现策略工厂的绝佳方式:
@Autowired
private Map<String, PayStrategy> strategyMap;
// key = Bean 名称, value = Bean 实例
// 比如 alipayPayStrategy → AlipayPayStrategy 实例Spring MVC 中的 HandlerMapping 也是策略模式——RequestMappingHandlerMapping、SimpleUrlHandlerMapping 等是不同的映射策略。
7. 适配器模式 —— HandlerAdapter
Spring MVC 支持多种 Controller 写法(注解式、接口式等),DispatcherServlet 通过 HandlerAdapter 来统一调用——不管你的 Controller 是什么类型,适配器帮你"翻译"成统一的调用方式。
8. 装饰器模式 —— Wrapper 类
HttpServletRequestWrapper:装饰HttpServletRequest,可以覆写某些方法TransactionAwareCacheDecorator:给缓存加上事务感知能力BeanDefinitionDecorator:给 BeanDefinition 添加额外配置
二、JDK 中用到的设计模式
JDK 本身也是设计模式的教科书级应用,这里按模式分类列举最典型的例子。
创建型
| 模式 | JDK 中的应用 | 说明 |
|---|---|---|
| 单例 | Runtime.getRuntime() | 饿汉式单例,每个 JVM 进程只有一个 Runtime 实例 |
| 工厂方法 | Calendar.getInstance() | 根据时区和地区返回不同的 Calendar 子类 |
| 工厂方法 | NumberFormat.getInstance() | 根据 Locale 返回不同的格式化器 |
| 建造者 | StringBuilder.append().toString() | 链式构建字符串 |
| 建造者 | Stream.of().filter().map().collect() | 链式构建数据处理管道 |
| 原型 | Object.clone() | 所有实现 Cloneable 的类都支持原型复制 |
结构型
| 模式 | JDK 中的应用 | 说明 |
|---|---|---|
| 代理 | java.lang.reflect.Proxy | JDK 动态代理的核心 API |
| 适配器 | InputStreamReader | 字节流 → 字符流的适配 |
| 适配器 | Arrays.asList() | 数组 → List 的适配 |
| 装饰器 | BufferedInputStream | 给 InputStream 增加缓冲能力 |
| 装饰器 | Collections.unmodifiableList() | 装饰成不可修改的 List |
| 装饰器 | Collections.synchronizedList() | 装饰成线程安全的 List |
| 外观 | SLF4J | 为 Log4j / Logback / JUL 提供统一接口 |
| 享元 | String 常量池 | 相同的字符串字面量共享一个对象 |
| 享元 | Integer.valueOf() | [-128, 127] 范围内返回缓存对象 |
| 享元 | Boolean.TRUE / FALSE | 只有两个实例,全局共享 |
行为型
| 模式 | JDK 中的应用 | 说明 |
|---|---|---|
| 策略 | Comparator | Collections.sort(list, comparator) 中的排序策略 |
| 策略 | RejectedExecutionHandler | 线程池满时的拒绝策略 |
| 模板方法 | AbstractList / AbstractMap | 定义集合操作骨架,子类填实现 |
| 模板方法 | HttpServlet.service() | 分派到 doGet / doPost 等钩子方法 |
| 观察者 | JButton.addActionListener() | Swing 的事件监听 |
| 迭代器 | java.util.Iterator | 所有集合类的统一遍历接口 |
| 责任链 | java.util.logging.Logger | 父子 Logger 形成日志处理链 |
| 责任链 | Servlet Filter 链 | FilterChain.doFilter() |
三、常见面试题精选
Q1:String 的设计用到了哪些设计模式?
String 用到了两种模式。享元模式:相同内容的字符串字面量在常量池中只存一份,String a = "hello"; String b = "hello"; 这里 a 和 b 指向同一个对象。不可变模式:String 类被 final 修饰不能被继承,内部的字符数组是 private final 的。所有看似"修改"的方法(replace()、substring() 等)都是创建新对象返回,原对象不变。不可变性带来了三大好处:线程安全(不需要同步就能多线程共享)、安全性(作为 HashMap 的 key 不会被篡改)、可缓存性(hash 值只需要计算一次)。
Q2:什么是不可变模式?包装类为什么不适合做锁?
不可变模式是指对象创建后状态不可改变。Java 的 String、Integer、Long 等都是不可变类,实现方式是类用 final 修饰(禁止继承覆写),属性用 private final 修饰(禁止修改),所有"修改"操作都返回新对象。
包装类不适合做锁是因为享元模式。Integer.valueOf(1) 在 [-128, 127] 范围内返回缓存对象,所以 Integer a = 1 和 Integer b = 1 是同一个对象。如果你在不同地方分别用 a 和 b 做锁,实际锁的是同一个对象,会产生意外的互斥。超出缓存范围的值(如 128)会创建新对象,此时又是两把不同的锁——行为不一致,非常危险。
Q3:什么是设计模式?学了有什么用?
设计模式是前人在软件开发中反复验证的通用解决方案。学习设计模式有四大收益:第一,降低试错成本——遇到类似问题直接借鉴成熟方案,不用从零摸索。第二,提高代码质量——设计模式天然遵循 SOLID 原则,代码可维护性和可扩展性更好。第三,提升沟通效率——"这里用策略模式"比描述具体实现方案高效得多,设计模式是开发者的通用语言。第四,理解框架源码——Spring、MyBatis、Netty 等主流框架大量使用设计模式,不懂模式就读不懂源码。
Q4:工作中最常用的设计模式组合是什么?
策略 + 工厂 + 模板方法是工作中最高频的三件套。以支付系统为例:定义 PayStrategy 接口作为策略抽象,每种支付方式写一个实现类。公共逻辑(参数校验、日志记录、异常处理)放到 AbstractPayStrategy 抽象类的模板方法中。用 PayStrategyFactory(可以借助 Spring 的 Map<String, PayStrategy> 自动注入)管理所有策略。调用时通过工厂获取策略实例,直接调用。新增支付方式只需加一个类加一个 @Component 注解。
Q5:如何提高代码的复用性和可维护性?
提高复用性的模式:工厂模式(集中管理创建逻辑,避免到处 new)、模板方法(抽取公共流程到父类)、装饰器(动态增加功能避免复制粘贴)、享元(共享对象避免重复创建)。
提高可维护性的模式:MVC(表示和处理分离,改界面不动逻辑)、策略(消灭 if-else,每种算法独立成类)、观察者(松耦合的事件通知,新增处理逻辑不改发布方)、外观(简化复杂子系统调用,降低使用门槛)。
核心思想就是两个字——解耦。耦合度越低,改一个地方影响的范围就越小,代码就越好维护。
Q6:MVC 模式的核心思想是什么?
MVC(Model-View-Controller)将应用程序分为三个职责清晰的层:
- Model(模型) —— 管理数据和业务逻辑。不关心数据怎么展示,只负责数据的增删改查和业务规则。
- View(视图) —— 管理界面展示。不直接操作数据,只负责把 Model 的数据用合适的形式呈现给用户。
- Controller(控制器) —— 管理用户输入和流程控制。接收用户请求,调用 Model 处理,把结果交给 View 展示。
核心思想是关注点分离——改界面不用动逻辑,改逻辑不用动界面。这带来了三大好处:可维护性(出 bug 能快速定位到对应层)、可测试性(Model 可以脱离 View 单独写单元测试)、可复用性(同一套 Model 可以同时给 Web、App、小程序使用)。
在 Spring MVC 中,Controller 就是带 @Controller 注解的类,Model 是 Service + Repository 层,View 是 Thymeleaf 模板或返回给前端的 JSON。
Q7:针对天气变化触发通知和推荐行程,用什么设计模式?
这个场景有两个关键需求:天气变化时自动通知用户、不同天气推荐不同行程。最佳方案是观察者模式 + 策略模式的组合。
观察者模式解决"天气变化时通知谁":天气服务(WeatherService)是被观察者,通知服务和推荐服务是观察者。天气变化时,WeatherService 遍历观察者列表发送通知。新增一种处理(比如"天气变化时更新首页图标")只需加一个观察者,不改 WeatherService 的代码。
策略模式解决"不同天气推荐什么":推荐服务内部维护一个策略映射表——晴天用 SunnyStrategy(推荐户外活动),雨天用 RainyStrategy(推荐室内活动),雪天用 SnowyStrategy(推荐在家休息)。新增天气类型只需加一个策略类。
// 推荐服务——观察者 + 策略
public class RecommendService implements WeatherObserver {
private Map<String, RecommendStrategy> strategies;
public void onWeatherChange(String weather) {
RecommendStrategy strategy = strategies.get(weather);
if (strategy != null) {
System.out.println(strategy.recommend());
}
}
}观察者负责"何时通知",策略负责"推荐什么"——各司其职,互不干扰。
Q8:设计模式的七大原则有哪些?怎么记忆?
七大原则可以分成两组来记忆。
第一组是 SOLID 五原则(名字本身就是记忆口诀):
- Single Responsibility(单一职责)—— 一个类只干一件事
- Open/Closed(开闭原则)—— 扩展开放、修改关闭
- Liskov Substitution(里氏替换)—— 子类能替代父类
- Interface Segregation(接口隔离)—— 接口要小而专注
- Dependency Inversion(依赖倒置)—— 面向接口编程
第二组是两大补充原则:
- 迪米特法则(最少知道原则)—— 对象之间尽量少了解彼此的内部细节
- 合成复用原则 —— 优先使用组合(has-a)而不是继承(is-a)
记忆技巧:SOLID 是"牢固的",用这五条原则打好地基。迪米特和合成复用是锦上添花的两条补充。
Q9:你在工作中是怎么使用设计模式的?
以一个常见的业务场景为例——优惠券系统。不同类型的优惠券(满减券、折扣券、兑换券)有不同的核算逻辑,但都有公共流程(校验有效期、校验使用条件、核算优惠金额、记录使用日志)。
设计方案采用策略 + 模板方法 + 工厂三件套:
// 策略接口
public interface CouponStrategy {
BigDecimal calculate(Order order, Coupon coupon);
}
// 抽象策略——用模板方法封装公共流程
public abstract class AbstractCouponStrategy implements CouponStrategy {
public final BigDecimal calculate(Order order, Coupon coupon) {
validateExpiry(coupon); // 公共:校验有效期
validateCondition(order, coupon); // 公共:校验使用条件
BigDecimal discount = doCalculate(order, coupon); // 各自实现
logUsage(order, coupon, discount); // 公共:记录日志
return discount;
}
protected abstract BigDecimal doCalculate(Order order, Coupon coupon);
// ... 公共方法省略
}
// 满减券策略
@Component("fullReductionCouponStrategy")
public class FullReductionStrategy extends AbstractCouponStrategy {
protected BigDecimal doCalculate(Order order, Coupon coupon) {
// 满200减30的逻辑
return order.getAmount().compareTo(coupon.getThreshold()) >= 0
? coupon.getDiscount() : BigDecimal.ZERO;
}
}
// 折扣券策略
@Component("percentageCouponStrategy")
public class PercentageStrategy extends AbstractCouponStrategy {
protected BigDecimal doCalculate(Order order, Coupon coupon) {
// 打八折的逻辑
return order.getAmount().multiply(BigDecimal.ONE.subtract(coupon.getRate()));
}
}
// 工厂
@Component
public class CouponStrategyFactory {
@Autowired
private Map<String, CouponStrategy> strategyMap;
public CouponStrategy getStrategy(String couponType) {
return strategyMap.get(couponType + "CouponStrategy");
}
}新增优惠券类型(比如"组合券")只需加一个策略类,不改任何已有代码。公共逻辑(校验、日志)只写一遍,不会重复。每个策略类代码量小、职责单一、易于测试。
小结
经过五篇文章的系统学习,我们完成了设计模式的全部旅程:
- 原则篇 —— SOLID + 迪米特 + 合成复用 = 七大原则,这是设计模式的理论地基
- 创建型 —— 单例、工厂、建造者、原型,解决"怎样优雅地创建对象"
- 结构型 —— 代理、适配器、装饰器、外观、桥接、组合、享元,解决"怎样灵活地组合对象"
- 行为型 —— 策略、观察者、模板方法、责任链、状态等 11 种,解决"对象之间怎样高效协作"
- 总结篇 —— Spring 和 JDK 中的设计模式实战,从"纸上谈兵"到"庖丁解牛"
最后送你一条学习设计模式的核心心法:
不要为了用模式而用模式。 先写最简单、最直接的代码。当你发现代码出现了"坏味道"——重复的代码、臃肿的类、僵硬的扩展、脆弱的耦合——再去想有没有合适的模式来重构。设计模式是治病的药方,没病别乱吃药。
掌握了设计模式,你看 Spring 源码会觉得"原来如此",读开源框架会觉得"设计真妙",做系统设计会觉得"思路清晰"。这才是学习设计模式最大的回报。