Skip to content

结构型设计模式深度解析

面试官:代理模式和装饰器模式有什么区别?

:表面上它们很像,都是包装对象增强功能。但本质目的不同:代理模式是控制访问,装饰器模式是增强功能。

面试官:能具体说说 Spring AOP 用的是哪种模式吗?为什么?

这道题考察的是对模式本质的理解。能说出”目的不同”并给出具体应用场景,才算真正掌握。


Q1:JDK 动态代理和 CGLIB 代理的区别?Spring AOP 如何选择?必考

Section titled “Q1:JDK 动态代理和 CGLIB 代理的区别?Spring AOP 如何选择?”

核心差异对比

对比维度JDK 动态代理CGLIB 代理
实现原理实现接口,Proxy.newProxyInstance()继承目标类,生成子类
使用要求目标类必须实现接口目标类不能是 final,方法不能是 final
性能(生成代理)快(直接生成)慢(ASM 字节码生成)
性能(方法调用)较慢(反射调用)快(直接调用)
Spring 默认有接口时(Spring 5 之前)Spring Boot 2.x 默认全用 CGLIB

JDK 动态代理实现

// 目标接口
public interface UserService {
User findById(Long id);
void save(User user);
}
// 目标实现
public class UserServiceImpl implements UserService {
@Override
public User findById(Long id) {
return userRepository.findById(id);
}
}
// 代理处理器
public class LoggingProxy implements InvocationHandler {
private final Object target;
public LoggingProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("[LOG] 调用方法:" + method.getName());
// 调用目标方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("[LOG] 方法返回:" + result);
return result;
}
}
// 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(), // 类加载器
new Class[]{UserService.class}, // 代理的接口
new LoggingProxy(new UserServiceImpl()) // 调用处理器
);
proxy.findById(1L); // 调用代理方法

CGLIB 代理实现

// 目标类(不需要接口)
public class UserService {
public User findById(Long id) {
return userRepository.findById(id);
}
}
// CGLIB 拦截器
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置增强
System.out.println("[CGLIB] 调用方法:" + method.getName());
// 调用目标方法(通过代理,避免递归)
Object result = proxy.invokeSuper(obj, args);
// 后置增强
System.out.println("[CGLIB] 方法返回:" + result);
return result;
}
}
// 创建代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class); // 设置父类
enhancer.setCallback(new CglibProxy()); // 设置拦截器
UserService proxy = (UserService) enhancer.create();
proxy.findById(1L);

代理生成原理图

┌────────────────────────────────────────────────┐
│ JDK 动态代理 │
├────────────────────────────────────────────────┤
│ │
│ 目标类 代理类 │
│ ┌─────────┐ ┌────────────────────┐ │
│ │UserService│ │$Proxy0 │ │
│ │ │ implements│UserService │ │
│ │findById()│ ◄──────┤ │ │
│ └─────────┘ │ - target │ │
│ │ - invoke() │ │
│ │ findById() { │ │
│ │ invoke(target, │ │
│ │ findById, args)│ │
│ │ } │ │
│ └────────────────────┘ │
│ │ │
│ ▼ │
│ InvocationHandler.invoke() │
│ │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ CGLIB 代理 │
├────────────────────────────────────────────────┤
│ │
│ 目标类 代理类(子类) │
│ ┌─────────┐ ┌────────────────────┐ │
│ │UserService│ │UserService$$Enhancer│ │
│ │ │ extends│byCGLIB │ │
│ │findById()│ ◄──────┤ │ │
│ └─────────┘ │ - CGLIB$findById$0│ │
│ │ (原方法) │ │
│ │ - findById() { │ │
│ │ interceptor. │ │
│ │ intercept() │ │
│ │ } │ │
│ └────────────────────┘ │
│ │ │
│ ▼ │
│ MethodInterceptor.intercept() │
│ │
└────────────────────────────────────────────────┘

Spring AOP 的选择策略

// Spring Boot 2.x 默认配置
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // true: 强制用 CGLIB
public class AopConfig { }
// 选择逻辑:
// 1. proxyTargetClass = true → 强制 CGLIB
// 2. 目标类实现接口 → JDK 动态代理(Spring 5 之前默认)
// 3. 目标类无接口 → CGLIB
// 4. Spring Boot 2.x 默认 proxyTargetClass = true

Q2:代理模式和装饰器模式的区别?必考

Section titled “Q2:代理模式和装饰器模式的区别?”

本质区别

对比维度代理模式装饰器模式
设计目的控制对对象的访问动态增强对象的功能
客户端感知客户端不知道代理存在客户端主动创建装饰器
对象关系代理内部固定持有目标对象装饰器可以动态叠加多层
关注点对象是否可以访问对象功能如何增强
典型应用Spring AOP、RPC 远程代理Java IO 流

代码对比

// ==================== 代理模式 ====================
// 目的:控制访问(权限、缓存、远程)
public interface UserService {
User findById(Long id);
}
public class UserServiceProxy implements UserService {
private UserService target; // 固定持有目标对象
private Cache cache;
@Override
public User findById(Long id) {
// 1. 控制访问:先查缓存
User cached = cache.get("user:" + id);
if (cached != null) {
return cached; // 缓存命中,不访问目标
}
// 2. 缓存未命中,访问目标
User user = target.findById(id);
cache.put("user:" + id, user);
return user;
}
}
// 客户端不知道代理存在
UserService service = context.getBean(UserService.class);
// ==================== 装饰器模式 ====================
// 目的:动态增强功能
public interface Coffee {
double cost();
String description();
}
public class SimpleCoffee implements Coffee {
@Override
public double cost() { return 10; }
@Override
public String description() { return "简单咖啡"; }
}
// 装饰器基类
public abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee; // 可以动态替换
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
// 具体装饰器:牛奶
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return coffee.cost() + 2; // 增强功能
}
@Override
public String description() {
return coffee.description() + " + 牛奶";
}
}
// 具体装饰器:糖
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return coffee.cost() + 1;
}
@Override
public String description() {
return coffee.description() + " + 糖";
}
}
// 客户端主动创建装饰器,可以叠加多层
Coffee coffee = new SimpleCoffee(); // 简单咖啡:10 元
coffee = new MilkDecorator(coffee); // + 牛奶:12 元
coffee = new SugarDecorator(coffee); // + 糖:13 元
System.out.println(coffee.description() + " = " + coffee.cost());
// 输出:简单咖啡 + 牛奶 + 糖 = 13.0

装饰器模式:动态叠加

┌────────────────────────────────────────────────┐
│ 装饰器的动态叠加 │
├────────────────────────────────────────────────┤
│ │
│ SimpleCoffee │
│ │ │
│ ▼ │
│ MilkDecorator(SimpleCoffee) │
│ │ │
│ ▼ │
│ SugarDecorator(MilkDecorator) │
│ │ │
│ ▼ │
│ 客户端调用 cost() │
│ │ │
│ ▼ │
│ SugarDecorator.cost() │
│ │ │
│ ├── MilkDecorator.cost() │
│ │ │ │
│ │ └── SimpleCoffee.cost() │
│ │ │ │
│ │ └── return 10 │
│ │ │
│ └── return 10 + 2 + 1 = 13 │
│ │
└────────────────────────────────────────────────┘

Java IO 流的装饰器模式

// 经典的装饰器模式应用
InputStream is = new FileInputStream("file.txt"); // 基础组件
is = new BufferedInputStream(is); // 装饰器1:缓冲
is = new GZIPInputStream(is); // 装饰器2:解压
is = new CipherInputStream(is, cipher); // 装饰器3:解密
// 可以任意组合装饰器,增强功能

Q3:适配器模式是什么?Spring MVC 如何用它统一处理器?高频

Section titled “Q3:适配器模式是什么?Spring MVC 如何用它统一处理器?”

定义:将一个类的接口转换成客户端期望的另一个接口,使原本不兼容的类可以一起工作。

三种实现方式

// 目标接口
public interface Target {
void request();
}
// 被适配者(已有类,接口不兼容)
public class Adaptee {
public void specificRequest() {
System.out.println("被适配者的特定请求");
}
}
// ==================== 方式1:类适配器(继承) ====================
public class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
super.specificRequest(); // 调用父类方法
}
}
// ==================== 方式2:对象适配器(组合,推荐) ====================
public class ObjectAdapter implements Target {
private Adaptee adaptee; // 组合
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest(); // 委托给被适配者
}
}
// ==================== 方式3:接口适配器(缺省适配) ====================
// 目标接口有很多方法,但客户端只需要其中几个
public interface TargetWithMultipleMethods {
void method1();
void method2();
void method3();
void method4();
}
// 抽象类提供空实现(适配器)
public abstract class AbstractAdapter implements TargetWithMultipleMethods {
@Override
public void method1() { }
@Override
public void method2() { }
@Override
public void method3() { }
@Override
public void method4() { }
}
// 客户端只需重写需要的方法
public class ConcreteAdapter extends AbstractAdapter {
@Override
public void method1() {
// 只实现需要的方法
}
}

类适配器 vs 对象适配器

对比维度类适配器(继承)对象适配器(组合)
灵活性低(只能适配一个类)高(可以适配多个类)
耦合度高(继承)低(组合)
重用性
推荐度⭐⭐⭐⭐⭐⭐⭐

Spring MVC 的适配器应用

// Spring MVC 的 HandlerAdapter:统一不同类型的处理器
// 目标接口:处理器适配器
public interface HandlerAdapter {
boolean supports(Object handler); // 是否支持该处理器
ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler);
}
// 被适配者1:Controller 接口
public interface Controller {
ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp);
}
// 适配器1:Controller 适配器
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof Controller;
}
@Override
public ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
return ((Controller) handler).handleRequest(req, resp);
}
}
// 被适配者2:HttpRequestHandler 接口
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest req, HttpServletResponse resp);
}
// 适配器2:HttpRequestHandler 适配器
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof HttpRequestHandler;
}
@Override
public ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
((HttpRequestHandler) handler).handleRequest(req, resp);
return null;
}
}
// DispatcherServlet 统一处理
public class DispatcherServlet {
private List<HandlerAdapter> handlerAdapters;
protected void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
Object handler = getHandler(req); // 获取处理器
// 找到支持的适配器
HandlerAdapter adapter = getHandlerAdapter(handler);
// 通过适配器统一调用
ModelAndView mv = adapter.handle(req, resp, handler);
}
}

Spring MVC 适配器架构

┌────────────────────────────────────────────────┐
│ Spring MVC 的处理器适配 │
├────────────────────────────────────────────────┤
│ │
│ DispatcherServlet │
│ │ │
│ ├── HandlerAdapter(统一接口) │
│ │ │ │
│ │ ├── SimpleControllerHandlerAdapter│
│ │ │ └── adapts Controller │
│ │ │ │
│ │ ├── HttpRequestHandlerAdapter │
│ │ │ └── adapts HttpRequestHandler│
│ │ │ │
│ │ ├── RequestMappingHandlerAdapter │
│ │ │ └── adapts @RequestMapping│
│ │ │ │
│ │ └── ServletHandlerAdapter │
│ │ └── adapts HttpServlet │
│ │ │
│ └── 所有处理器都统一通过适配器调用 │
│ │
└────────────────────────────────────────────────┘

Q4:外观模式是什么?与迪米特法则的关系?中频

Section titled “Q4:外观模式是什么?与迪米特法则的关系?”

定义:为子系统中的一组接口提供一个统一的入口,外观模式定义了一个高层接口,这个接口使得子系统更容易使用。

应用场景:简化复杂子系统的使用。

// 子系统:多个复杂的服务
public class InventoryService {
public boolean checkStock(String productId, int quantity) {
// 复杂的库存检查逻辑
return true;
}
}
public class PaymentService {
public boolean charge(String orderId, BigDecimal amount) {
// 复杂的支付逻辑
return true;
}
}
public class ShippingService {
public void ship(String orderId, String address) {
// 复杂的物流逻辑
}
}
public class NotificationService {
public void sendEmail(String email, String message) {
// 发送邮件
}
public void sendSms(String phone, String message) {
// 发送短信
}
}
// ✅ 外观模式:提供简化的统一入口
public class OrderFacade {
private InventoryService inventoryService;
private PaymentService paymentService;
private ShippingService shippingService;
private NotificationService notificationService;
// 简化的下单流程
public boolean placeOrder(Order order) {
// 1. 检查库存
if (!inventoryService.checkStock(order.getProductId(), order.getQuantity())) {
return false;
}
// 2. 扣款
if (!paymentService.charge(order.getId(), order.getTotal())) {
return false;
}
// 3. 发货
shippingService.ship(order.getId(), order.getAddress());
// 4. 发送通知
notificationService.sendEmail(order.getEmail(), "订单已发货");
notificationService.sendSms(order.getPhone(), "订单已发货");
return true;
}
}
// 客户端使用:非常简单
OrderFacade facade = new OrderFacade();
facade.placeOrder(order); // 一行代码完成下单

外观模式 vs 适配器模式

对比维度外观模式适配器模式
目的简化接口转换接口
接口数量多个接口 → 一个接口一个接口 → 另一个接口
使用场景子系统复杂,需要简化接口不兼容,需要转换
是否新增功能否(只是封装)否(只是转换)

与迪米特法则的关系:外观模式是迪米特法则的最佳实践。客户端只需要与外观交互,不需要了解子系统的内部结构。


Q5:享元模式是什么?Integer.valueOf() 如何体现?高频

Section titled “Q5:享元模式是什么?Integer.valueOf() 如何体现?”

定义:运用共享技术有效地支持大量细粒度对象的复用。通过共享对象减少内存占用。

核心概念

  • 内部状态(Intrinsic):可以共享的状态
  • 外部状态(Extrinsic):不可共享的状态,由客户端传入

Integer 的享元模式

// Integer.valueOf() 的实现
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high) {
return IntegerCache.cache[i + (-IntegerCache.low)]; // 返回缓存对象
}
return new Integer(i); // 超出缓存范围,新建对象
}
// Integer 内部缓存
private static class IntegerCache {
static final int low = -128;
static final int high = 127; // 默认缓存 -128 ~ 127
static final Integer cache[];
static {
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++) {
cache[k] = new Integer(j++); // 预先创建缓存对象
}
}
}
// 测试
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a == b); // true:同一个缓存对象
Integer c = Integer.valueOf(200);
Integer d = Integer.valueOf(200);
System.out.println(c == d); // false:超出缓存范围,新建对象

享元模式结构

┌────────────────────────────────────────────────┐
│ Integer 的享元缓存 │
├────────────────────────────────────────────────┤
│ │
│ IntegerCache │
│ │ │
│ ├── cache[](内部状态,可共享) │
│ │ ├── cache[0] → Integer(-128) │
│ │ ├── cache[1] → Integer(-127) │
│ │ ├── ... │
│ │ └── cache[255] → Integer(127) │
│ │ │
│ └── valueOf(int i) │
│ ├── -128 ≤ i ≤ 127 → 返回缓存 │
│ └── 其他 → 新建 Integer │
│ │
│ 客户端代码 │
│ ├── Integer.valueOf(100) → 缓存命中 │
│ └── Integer.valueOf(200) → 新建对象 │
│ │
└────────────────────────────────────────────────┘

享元模式的应用场景

应用内部状态(共享)外部状态(不共享)
Integer数值
String字符串内容无(String.intern())
线程池线程对象任务(Runnable)
连接池连接对象SQL 语句

性能对比

// 不用享元:每次新建对象
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
Integer num = new Integer(100); // 每次新建
}
long end = System.currentTimeMillis();
System.out.println("新建对象耗时:" + (end - start) + "ms"); // 约 500ms
// 使用享元:复用缓存对象
start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
Integer num = Integer.valueOf(100); // 从缓存获取
}
end = System.currentTimeMillis();
System.out.println("享元模式耗时:" + (end - start) + "ms"); // 约 50ms
// 性能提升约 10 倍

Q6:组合模式是什么?Java 集合框架如何应用?中频

Section titled “Q6:组合模式是什么?Java 集合框架如何应用?”

定义:将对象组合成树形结构以表示”部分-整体”的层次结构,使客户端对单个对象和组合对象的使用具有一致性。

应用场景:树形结构(文件系统、组织架构、菜单)。

// 抽象组件
public interface FileSystemNode {
void display(); // 统一接口
int getSize();
}
// 叶子节点(文件)
public class File implements FileSystemNode {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public void display() {
System.out.println("文件:" + name);
}
@Override
public int getSize() {
return size;
}
}
// 组合节点(文件夹)
public class Directory implements FileSystemNode {
private String name;
private List<FileSystemNode> children = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void add(FileSystemNode node) {
children.add(node);
}
@Override
public void display() {
System.out.println("文件夹:" + name);
for (FileSystemNode child : children) {
child.display(); // 递归显示子节点
}
}
@Override
public int getSize() {
int total = 0;
for (FileSystemNode child : children) {
total += child.getSize(); // 递归计算大小
}
return total;
}
}
// 客户端:统一处理文件和文件夹
FileSystemNode root = new Directory("根目录");
FileSystemNode folder1 = new Directory("文件夹1");
FileSystemNode folder2 = new Directory("文件夹2");
folder1.add(new File("文件1.txt", 100));
folder1.add(new File("文件2.txt", 200));
folder2.add(new File("文件3.txt", 300));
root.add(folder1);
root.add(folder2);
root.add(new File("文件4.txt", 400));
root.display(); // 递归显示所有文件和文件夹
System.out.println("总大小:" + root.getSize()); // 1000

组合模式结构图

┌────────────────────────────────────────────────┐
│ 组合模式:树形结构 │
├────────────────────────────────────────────────┤
│ │
│ FileSystemNode(抽象组件) │
│ ├── display() │
│ └── getSize() │
│ │ │
│ ├── File(叶子节点) │
│ │ ├── display() │
│ │ └── getSize() │
│ │ │
│ └── Directory(组合节点) │
│ ├── List<FileSystemNode> │
│ ├── add() │
│ ├── display() │
│ │ └── 递归调用子节点 │
│ └── getSize() │
│ └── 递归累加子节点大小 │
│ │
│ 客户端:无需区分 File 和 Directory │
│ └── 统一调用 display() 和 getSize() │
│ │
└────────────────────────────────────────────────┘

Java 集合框架的组合模式

// Java 集合的组合模式
Collection<String> single = Collections.singleton("单个元素"); // 叶子节点
Collection<String> list = new ArrayList<>(); // 组合节点
list.add("元素1");
list.add("元素2");
// 客户端统一处理
void process(Collection<String> collection) {
for (String item : collection) {
System.out.println(item);
}
// 无论单个元素还是集合,统一遍历
}
process(single); // 单个元素
process(list); // 集合

六种模式对比

模式核心问题关键特征典型应用
代理控制访问包装对象,控制访问权限Spring AOP、RPC
装饰器动态增强功能可叠加的包装器Java IO 流
适配器接口不兼容转换接口Spring MVC HandlerAdapter
外观子系统复杂统一入口,简化接口JdbcTemplate
享元对象数量多共享内部状态Integer 缓存、连接池
组合树形结构统一处理叶子和组合文件系统、菜单

选择决策树

┌────────────────────────────────────────────────┐
│ 结构型模式选择 │
├────────────────────────────────────────────────┤
│ │
│ 需要控制对象访问? │
│ └── 是 → 代理模式 │
│ │
│ 需要动态增强功能? │
│ └── 是 → 装饰器模式 │
│ │
│ 接口不兼容需要转换? │
│ └── 是 → 适配器模式 │
│ │
│ 需要简化复杂子系统? │
│ └── 是 → 外观模式 │
│ │
│ 大量相似对象,内存占用高? │
│ └── 是 → 享元模式 │
│ │
│ 需要处理树形结构? │
│ └── 是 → 组合模式 │
│ │
└────────────────────────────────────────────────┘

常见追问

  1. Q:代理模式会增加系统复杂度,值得吗? A:值得。代理可以实现 AOP、缓存、权限控制、远程调用等横切关注点,将这些逻辑与业务逻辑分离,提高代码可维护性。

  2. Q:装饰器模式为什么比继承更灵活? A:继承是静态的,装饰器是动态的。装饰器可以在运行时任意组合,而继承在编译时就确定了。

  3. Q:享元模式会不会导致线程安全问题? A:享元对象必须是不可变的,只包含内部状态。外部状态由客户端传入,不存在线程安全问题。

  4. Q:外观模式和中介者模式有什么区别? A:外观模式是单向的(客户端调用外观),中介者模式是双向的(同事类互相通信)。外观模式简化接口,中介者模式解耦同事类。