一,结构性模式介绍
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,
后者采用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构性模式分为七种:代理模式,适配器模式,装饰者模式,桥接模式,外观模式,组合模式,享元模式。
二,代理模式
2.1 什么是代理模式?
核心思想:为一个对象提供一个替身或占位符,以控制对这个对象的访问。
通俗理解:就像明星和经纪人的关系。你想请明星演出,你不会直接联系明星,而是先联系他的经纪人。经纪人会处理一些前置工作(谈合同、排档期)和后置工作(收钱、善后),在合适的时机才会让明星本人出场。这个“经纪人”就是明星的“代理”。
作用:通过引入代理对象,可以在不改变原始对象(目标对象)的情况下,扩展其功能。主要功能包括:权限控制、延迟加载、日志记录、性能监控、事务管理等。
Java 中实现代理主要有三种:静态代理、基于 JDK 的动态代理 和 基于 CGLib 的动态代理。
2.2 静态代理
这种方式是“硬编码”的,在编译期就确定了代理关系。
角色
- 抽象主题(Subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject):实现了抽象主题的具体类,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理类(Proxy):也实现了抽象主题,它包含对真实主题的引用,可以访问、控制或扩展真实主题的功能。
代码示例
// 1. 抽象主题接口
interface Star {
void perform();
}
// 2. 真实主题 - 周杰伦
class JayChou implements Star {
@Override
public void perform() {
System.out.println("周杰伦在唱歌...");
}
}
// 3. 代理类 - 经纪人
class Agent implements Star {
// 持有真实对象的引用
private Star target;
public Agent(Star target) {
this.target = target;
}
@Override
public void perform() {
System.out.println("经纪人谈合同、安排档期..."); // 前置增强
target.perform(); // 调用真实对象的方法
System.out.println("经纪人收钱、处理事后工作..."); // 后置增强
}
}
// 4. 客户端使用
public class StaticProxyDemo {
public static void main(String[] args) {
Star jay = new JayChou(); // 创建真实对象
Star agent = new Agent(jay); // 创建代理对象,传入真实对象
agent.perform(); // 你接触的是代理对象,但实际干活的是真实对象
}
}
输出结果:
经纪人谈合同、安排档期...
周杰伦在唱歌...
经纪人收钱、处理事后工作...
静态代理的优缺点:
- 优点:简单、直观,无需反射,性能较好。
- 缺点:
- 如果接口增加方法,代理类和真实类都需要实现,违反了“开闭原则”。
- 每个真实类都需要创建一个对应的代理类,当真实类很多时,会导致类数量爆炸,难以维护。
2.3 JDK动态代理
JDK 动态代理是 Java 标准库提供的,在运行时动态生成代理类的机制。它基于接口来实现代理,不需要我们手动编写具体的代理类。
核心:在运行时动态创建接口的实现类
核心组件
要理解 JDK 动态代理,需要掌握三个核心概念:
InvocationHandler接口:代理对象的调用处理器Proxy类:用于创建代理实例的工具类- 目标对象:被代理的原始对象
代码示例
-
定义接口
// 用户服务接口 public interface UserService { void addUser(String username); void deleteUser(String username); String getUser(String username); } -
实现接口(目标对象)
// 用户服务实现类 - 这是我们要代理的真实对象 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("添加用户: " + username); // 模拟业务逻辑 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void deleteUser(String username) { System.out.println("删除用户: " + username); } @Override public String getUser(String username) { System.out.println("查询用户: " + username); return "用户信息: " + username; } } -
实现调用处理器
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; public class LoggingHandler implements InvocationHandler { // 持有被代理对象的引用 private final Object target; public LoggingHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强:记录方法开始时间 long startTime = System.currentTimeMillis(); System.out.println("【开始执行】方法: " + method.getName() + ", 参数: " + Arrays.toString(args)); try { // 调用真实对象的方法 Object result = method.invoke(target, args); // 后置增强:记录方法执行结果 System.out.println("【执行成功】方法: " + method.getName() + ", 返回值: " + result); return result; } catch (Exception e) { // 异常增强:记录异常信息 System.out.println("【执行失败】方法: " + method.getName() + ", 异常: " + e.getMessage()); throw e; } finally { // 最终增强:记录方法执行时间 long endTime = System.currentTimeMillis(); System.out.println("【执行结束】方法: " + method.getName() + ", 耗时: " + (endTime - startTime) + "ms"); System.out.println("-----------------------------------"); } } } -
创建代理对象并使用
import java.lang.reflect.Proxy; public class DynamicProxyDemo { public static void main(String[] args) { // 创建真实对象 UserService realService = new UserServiceImpl(); // 创建调用处理器 InvocationHandler handler = new LoggingHandler(realService); // 创建代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), // 类加载器 realService.getClass().getInterfaces(), // 接口数组 handler // 调用处理器 ); // 使用代理对象 System.out.println("代理对象类型: " + proxy.getClass().getName()); System.out.println("==================================="); // 调用方法 - 这些调用都会被代理拦截 proxy.addUser("张三"); proxy.getUser("张三"); proxy.deleteUser("李四"); // 验证代理对象确实实现了接口 System.out.println("是否是UserService实例: " + (proxy instanceof UserService)); } } -
运行结果分析
运行上面的代码,你会看到类似这样的输出
代理对象类型: com.sun.proxy.$Proxy0 =================================== 【开始执行】方法: addUser, 参数: [张三] 添加用户: 张三 【执行成功】方法: addUser, 返回值: null 【执行结束】方法: addUser, 耗时: 105ms ----------------------------------- 【开始执行】方法: getUser, 参数: [张三] 查询用户: 张三 【执行成功】方法: getUser, 返回值: 用户信息: 张三 【执行结束】方法: getUser, 耗时: 0ms ----------------------------------- 【开始执行】方法: deleteUser, 参数: [李四] 删除用户: 李四 【执行成功】方法: deleteUser, 返回值: null 【执行结束】方法: deleteUser, 耗时: 0ms ----------------------------------- 是否是UserService实例: true -
核心原理
Proxy.newProxyInstance()方法参数:- 类加载器:用于加载动态生成的代理类
- 接口数组:代理类要实现的接口
- InvocationHandler:方法调用的处理逻辑
InvocationHandler.invoke()方法参数:- proxy:代理对象本身(通常不需要使用)
- method:被调用的方法
- args:方法参数
-
与静态代理的区别
方面 静态代理 JDK动态代理 创建时机 编译期 运行期 实现方式 代理类需要显式定义,实现与目标类相同的接口。 通过 java.lang.reflect.Proxy类的newProxyInstance方法动态生成。源码存在性 有实际的 .java源文件和.class字节码文件。没有实际的 .java文件,其.class字节码是在运行时动态生成,并直接加载到JVM中。灵活性 低。每个代理类只能为一个接口服务。如果有多个接口需要代理,需要编写大量的代理类。 高。一个 InvocationHandler可以代理多种不同类型的接口,通用性强。维护性 差。如果接口增加方法,目标类和代理类都需要同步修改。 好。接口增加方法, InvocationHandler的invoke方法不需要修改,它会自动代理所有接口方法。依赖关系 代理类直接依赖目标对象。 代理类依赖 InvocationHandler对象,由该 handler 去调用目标对象。技术要求 仅需基本的Java面向对象知识。 需要理解反射机制和动态代理的API。
2.4 CGLib动态代理
因为 JDK 动态代理只能基于接口,所以后来出现了 CGLib 这样的基于继承的动态代理库,它可以代理未实现任何接口的普通类。
Spring AOP 默认策略是:如果目标对象实现了接口,则使用 JDK 动态代理;如果没有,则使用 CGLib。
CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它通过继承目标类并在运行时生成子类来实现代理。这意味着它可以代理没有实现接口的普通类。
CGLIB vs JDK 动态代理
| 特性 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 代理方式 | 实现接口 | 继承类 |
| 目标要求 | 必须实现接口 | 可以是普通类 |
| 性能 | 较慢(反射调用) | 较快(方法调用) |
| 限制 | 不能代理类 | 不能代理final类/方法,不能代理private方法 |
| 依赖 | JDK自带 | 需要额外jar包 |
环境准备
首先需要添加 CGLIB 依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
完整代码示例
-
创建目标类(不需要接口)
// 用户服务类 - 没有实现任何接口! public class UserService { public void addUser(String username) { System.out.println("添加用户: " + username); // 模拟业务逻辑 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } public void deleteUser(String username) { System.out.println("删除用户: " + username); } public String getUser(String username) { System.out.println("查询用户: " + username); return "用户信息: " + username; } // final方法 - CGLIB无法代理 public final void finalMethod() { System.out.println("这是final方法"); } // private方法 - CGLIB不会代理 private void privateMethod() { System.out.println("这是private方法"); } } -
实现方法拦截器
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.Arrays; public class LoggingInterceptor implements MethodInterceptor { /** * @param obj 代理对象(增强后的对象) * @param method 被拦截的方法 * @param args 方法参数 * @param proxy 用于调用父类(原始)方法 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 前置增强 long startTime = System.currentTimeMillis(); System.out.println("【CGLIB代理】开始执行方法: " + method.getName() + ", 参数: " + Arrays.toString(args)); try { // 调用原始方法 - 注意这里使用MethodProxy而不是Method Object result = proxy.invokeSuper(obj, args); // 后置增强 System.out.println("【CGLIB代理】执行成功: " + method.getName() + ", 返回值: " + result); return result; } catch (Exception e) { // 异常增强 System.out.println("【CGLIB代理】执行失败: " + method.getName() + ", 异常: " + e.getMessage()); throw e; } finally { // 最终增强 long endTime = System.currentTimeMillis(); System.out.println("【CGLIB代理】执行结束: " + method.getName() + ", 耗时: " + (endTime - startTime) + "ms"); System.out.println("-----------------------------------"); } } } -
创建代理对象并使用
import net.sf.cglib.proxy.Enhancer; public class CglibProxyDemo { public static void main(String[] args) { // 创建Enhancer对象 - CGLIB的核心类 Enhancer enhancer = new Enhancer(); // 设置父类(被代理的类) enhancer.setSuperclass(UserService.class); // 设置回调(方法拦截器) enhancer.setCallback(new LoggingInterceptor()); // 创建代理对象 UserService proxy = (UserService) enhancer.create(); // 使用代理对象 System.out.println("代理对象类型: " + proxy.getClass().getName()); System.out.println("父类: " + proxy.getClass().getSuperclass().getName()); System.out.println("==================================="); // 调用方法 proxy.addUser("王五"); proxy.getUser("王五"); proxy.deleteUser("赵六"); // 测试final方法 proxy.finalMethod(); // 验证继承关系 System.out.println("是否是UserService子类: " + (proxy instanceof UserService)); } } -
运行结果分析
代理对象类型: com.example.UserService$$EnhancerByCGLIB$$12345678 父类: com.example.UserService =================================== 【CGLIB代理】开始执行方法: addUser, 参数: [王五] 添加用户: 王五 【CGLIB代理】执行成功: addUser, 返回值: null 【CGLIB代理】执行结束: addUser, 耗时: 105ms ----------------------------------- 【CGLIB代理】开始执行方法: getUser, 参数: [王五] 查询用户: 王五 【CGLIB代理】执行成功: getUser, 返回值: 用户信息: 王五 【CGLIB代理】执行结束: getUser, 耗时: 0ms ----------------------------------- 【CGLIB代理】开始执行方法: deleteUser, 参数: [赵六] 删除用户: 赵六 【CGLIB代理】执行成功: deleteUser, 返回值: null 【CGLIB代理】执行结束: deleteUser, 耗时: 0ms ----------------------------------- 这是final方法 是否是UserService子类: true
CGLIB 有以下限制:
- 不能代理final类
- 不能代理final方法
- 不能代理private方法(但可以通过配置改变)
- 需要额外依赖
- 构造方法不会被拦截
这些不能代理的原因是因为,通过的是继承实现的代理,而 java 的继承无法重写 private 方法,无法重写 final 方法
CGLIB 代理的核心要点:
- 基于继承:通过生成目标类的子类来实现代理
- 使用Enhancer:核心工具类,用于创建代理对象
- MethodInterceptor:方法拦截器,类似JDK的
InvocationHandler - MethodProxy.invokeSuper():调用原始方法的高效方式
- 适用场景:代理没有接口的类,性能要求较高的场景
CGLIB 在 Spring、Hibernate 等主流框架中广泛应用,特别是在需要代理普通类的场景下。
2.5 三种代理对比
-
静态代理(Static Proxy)
// 1. 定义接口 public interface UserService { void addUser(String username); } // 2. 真实实现类 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("添加用户: " + username); } } // 3. 代理类(手动编写) public class UserServiceProxy implements UserService { private UserService target; // 持有真实对象的引用 public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser(String username) { // 前置增强 System.out.println("开始事务..."); long start = System.currentTimeMillis(); // 调用真实方法 target.addUser(username); // 后置增强 long end = System.currentTimeMillis(); System.out.println("提交事务..."); System.out.println("耗时: " + (end - start) + "ms"); } } // 4. 使用 public class StaticProxyDemo { public static void main(String[] args) { UserService realService = new UserServiceImpl(); UserService proxy = new UserServiceProxy(realService); proxy.addUser("张三"); } }原理分析
- 编译时生成:代理类在编译前就已经存在
- 手动编码:需要为每个被代理的类编写对应的代理类
- 继承/实现关系:代理类和目标类实现相同的接口
-
JDK 动态代理(JDK Dynamic Proxy)
底层原理
// 核心原理模拟 - Spring内部的简化实现 public class JdkProxySimulator { /** * 模拟Proxy.newProxyInstance的内部实现 */ public static Object createProxy(Object target) throws Exception { // 1. 生成代理类的字节码 byte[] proxyClassBytes = generateProxyClass(target); // 2. 定义类 Class<?> proxyClass = defineClass(proxyClassBytes); // 3. 创建实例 Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); return constructor.newInstance(new LoggingHandler(target)); } /** * 模拟生成的代理类(实际由JDK动态生成) */ public class $Proxy0 extends Proxy implements UserService { private static Method m1; // addUser方法 static { try { m1 = UserService.class.getMethod("addUser", String.class); } catch (NoSuchMethodException e) { throw new Error(e); } } public $Proxy0(InvocationHandler h) { super(h); } @Override public void addUser(String username) { try { // 调用InvocationHandler的invoke方法 h.invoke(this, m1, new Object[]{username}); } catch (Throwable e) { throw new RuntimeException(e); } } } } // 实际使用 public class JdkProxyDemo { public static void main(String[] args) { UserService realService = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), new Class[]{UserService.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method: " + method.getName()); Object result = method.invoke(realService, args); System.out.println("After method: " + method.getName()); return result; } } ); proxy.addUser("李四"); } }字节码层面分析
// 生成的代理类反编译结果大致如下: public final class $Proxy1 extends Proxy implements UserService { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy1(InvocationHandler var1) { super(var1); } public final void addUser(String var1) { try { // 关键:调用InvocationHandler的invoke方法 super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("com.example.UserService").getMethod("addUser", Class.forName("java.lang.String")); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } -
CGLIB 动态代理(CGLIB Dynamic Proxy)
**底层原理**
```java
// CGLIB核心原理模拟
public class CglibSimulator {
/**
* 模拟Enhancer.create()的内部实现
*/
public static Object createProxy(Class<?> superclass) {
// 1. 创建字节码生成器
ClassGenerator generator = new ClassGenerator();
// 2. 设置父类
generator.setSuperClass(superclass);
// 3. 添加方法拦截器
generator.addMethodInterceptor("addUser", new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After: " + method.getName());
return result;
}
});
// 4. 生成字节码并创建实例
return generator.create();
}
}
// 实际生成的CGLIB代理类大致如下:
public class UserService$$EnhancerByCGLIB$$123456 extends UserService {
private MethodInterceptor interceptor;
private static final Method CGLIB$addUser$0$Method;
private static final MethodProxy CGLIB$addUser$0$Proxy;
static {
try {
// 获取目标方法
CGLIB$addUser$0$Method = UserService.class.getMethod("addUser", String.class);
// 创建方法代理(FastClass机制)
CGLIB$addUser$0$Proxy = MethodProxy.create(
UserService.class,
UserService$$EnhancerByCGLIB$$123456.class,
"()V", "addUser", "CGLIB$addUser$0"
);
} catch (NoSuchMethodException e) {
throw new Error(e);
}
}
// 重写父类方法
public final void addUser(String username) {
MethodInterceptor tmp = this.interceptor;
if (tmp != null) {
// 调用拦截器
tmp.intercept(this, CGLIB$addUser$0$Method, new Object[]{username}, CGLIB$addUser$0$Proxy);
} else {
// 直接调用父类方法
super.addUser(username);
}
}
// FastClass直接调用(绕过反射)
public final void CGLIB$addUser$0(String username) {
super.addUser(username);
}
}
```
**FastClass机制原理**
**核心原理**:**FastClass通过方法索引来直接调用,避免反射查找的开销**。它为每个方法分配一个唯一的索引,通过索引直接定位并调用方法。
```java
// FastClass模拟 - 避免反射调用开销
public class UserServiceFastClass {
private static final int INDEX_addUser = 0;
private static final int INDEX_getUser = 1;
/**
* 通过索引直接调用方法,避免反射
*/
public Object invoke(int index, Object obj, Object[] args) {
switch (index) {
case INDEX_addUser:
((UserService) obj).addUser((String) args[0]);
return null;
case INDEX_getUser:
return ((UserService) obj).getUser((String) args[0]);
default:
throw new IllegalArgumentException("Invalid method index");
}
}
}
```
4. 三种代理的详细对比
**原理对比表**
| 特性 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
| :------------- | :----------- | :----------- | :---------------- |
| **生成时机** | 编译时 | 运行时 | 运行时 |
| **生成方式** | 手动编码 | Proxy类生成 | Enhancer生成 |
| **字节码操作** | 无 | 有 | 有 |
| **实现机制** | 实现相同接口 | 实现相同接口 | 继承目标类 |
| **方法调用** | 直接调用 | 反射调用 | FastClass直接调用 |
5. 三种代理的核心区别:
1. **静态代理**:编译时确定,手动编码,控制精确但繁琐
2. **JDK动态代理**:基于接口,运行时生成,使用反射调用
3. **CGLIB代理**:基于继承,运行时生成,使用**FastClass**优化
6. 现代开发建议:
- Spring Boot项目通常使用**CGLIB**作为默认代理
- 框架开发优先考虑JDK动态代理(面向接口)
- 特殊场景考虑静态代理(需要精确控制时)
- 理解原理有助于解决事务失效等典型问题
2.6 FastClass详解
提问:为什么方法索引能避免反射并加快速度?
2.6.1 反射的瓶颈在哪里?
反射调用的详细步骤:
public class ReflectionSlowSteps {
public static void main(String[] args) throws Exception {
UserService service = new UserService();
Method method = UserService.class.getMethod("getUser", String.class);
// 看似简单的一行反射调用,背后发生了:
method.invoke(service, "123");
}
}
反射调用背后的复杂流程:
- 方法查找:通过方法名和参数类型查找Method对象
- 权限检查:检查调用者是否有访问该方法的权限
- 参数装箱:将基本类型参数装箱为Object
- 类型转换:验证参数类型是否匹配
- 方法分派:通过JNI调用本地方法
- 实际调用:最终调用目标方法
- 返回值处理:拆箱和类型转换
2.6.2 FastClass如何解决这些问题?
FastClass 的工作机制:
// 假设我们有一个目标类
public class UserService {
public String getUser(String id) { return "user_" + id; }
public void updateUser(String id) { /* 更新逻辑 */ }
}
// FastClass为这个类生成"方法索引表"
public class UserServiceFastClass extends FastClass {
// 关键:编译时确定的方法索引
private static final int INDEX_getUser = 0;
private static final int INDEX_updateUser = 1;
// 方法签名到索引的映射表(编译时生成)
private static final Map<String, Integer> METHOD_INDEX_MAP = Map.of(
"getUser(String)": 0,
"updateUser(String)": 1
);
/**
* 通过索引直接调用 - 避免反射!
*/
@Override
public Object invoke(int index, Object obj, Object[] args) {
UserService target = (UserService) obj;
switch (index) { // JVM对switch有深度优化
case INDEX_getUser:
return target.getUser((String) args[0]); // 直接方法调用!
case INDEX_updateUser:
target.updateUser((String) args[0]); // 直接方法调用!
return null;
default:
throw new IllegalArgumentException("Invalid index: " + index);
}
}
/**
* 获取方法索引(只需调用一次)
*/
public int getIndex(String methodName, Class[] paramTypes) {
String key = methodName + "(" + Arrays.stream(paramTypes)
.map(Class::getSimpleName)
.collect(Collectors.joining(",")) + ")";
return METHOD_INDEX_MAP.getOrDefault(key, -1);
}
}
2.6.3 详细步骤对比
public class PerformanceComparison {
public void reflectionCall(Object target, Method method, Object[] args) throws Exception {
// 反射调用 - 慢路径
method.invoke(target, args);
// 背后发生:
// 1. 检查methodAccessor是否存在
// 2. 验证参数数量和类型
// 3. 进行权限检查
// 4. 通过JNI调用本地方法
// 5. 本地方法中查找实际方法地址
// 6. 调用实际方法
}
public void fastClassCall(Object target, FastClass fastClass, int methodIndex, Object[] args) {
// FastClass调用 - 快路径
fastClass.invoke(methodIndex, target, args);
// 背后发生:
// 1. 直接switch到对应case(JVM优化为跳转表)
// 2. 直接调用目标方法(就像手写代码一样)
}
}
2.6.4 总结
为什么方法索引能大幅提升性能:
- 避免方法查找 - 索引直接对应方法,无需名称查找
- 避免权限检查 - 生成时已验证,运行时直接调用
- 避免反射机制 - 直接方法调用 vs 反射的JNI调用
- 利于JIT优化 - switch语句可以被深度优化
- 减少调用层级 - 从多层间接调用变为直接调用
本质上是: 用空间换时间的思想,通过预生成方法索引映射表,将运行时的动态查找转换为编译时的静态映射,从而避免了反射的各种运行时开销。
三,适配器模式
定义:将一个类的接口转换成客户希望的外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
3.1 结构
适配器模式 (Adapter) 包含以下主要角色:
- **目标(target)接口:**当前系统业务所期待的接口,它可以是抽象类或接口,即你想要的接口
- **适配者(Adapter)类:**它是被访问和适配的现存组件库中的组件接口,即旧的当前需要转换的类
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访
问适配者。
生活化比喻:出国旅行充电器
想象一下,你从中国(当前系统) 去日本(现存组件库) 旅行。
- 你的中国国标插头充电器(目标接口):这是你习惯使用的,也是你的手机期待插入的接口。它有两个扁平的插脚。
- 日本的墙壁插座(适配者类):这是日本本地存在的、提供电力的组件。它有两个扁平的插孔,但没有接地孔,形状和中国的不同。
- 电源转换插头(适配器类):这是一个小装置,一头是符合日本插座标准的插脚,另一头是提供中国标准插孔的接口。
3.2 代码实现
-
目标接口 -
ChinaPowerOutlet这是你的中国设备所“期待”的接口。它定义了“供电”这个方法。你的手机只知道要通过这个接口来充电。
// 目标接口:中国标准电源接口 public interface ChinaPowerOutlet { // 提供电力,返回电压(比如220V) String providePower(); } -
适配者类 -
JapanesePowerSource这是在日本已经存在的、能正常工作的组件。它有自己的供电方式,但它的接口(插孔形状)不符合你的中国设备的要求。
// 适配者类:日本标准的电源 public class JapanesePowerSource { // 日本电源的原始方法,接口不同 public String supplyPower() { return "100V 交流电"; } }注意:它的方法名是
supplyPower(),而不是目标接口的providePower()。 -
适配器类 -
PowerAdapter
适配器的作用就是**站在中间做翻译和转换工作**。它需要实现目标接口,这样你的中国设备才能把它当成一个“中国插座”来使用。同时,它内部必须持有一个**对“日本电源”的引用**,以便调用其真正的功能。
有两种方式实现适配器:**类适配器(继承)** 和**对象适配器(组合)**,更推荐使用的是**对象适配器**,因为它更灵活。
- **类适配器**
通过继承 - 需要多重继承,Java中不直接支持,但可通过继承类并实现接口来模拟
```java
// 适配器类:电源转换器 (类适配器模式 - 继承方式)
// 它继承了被适配者,同时实现了目标接口
public class ClassPowerAdapter extends JapanesePowerSource implements ChinaPowerOutlet {
@Override
public String providePower() {
// 直接调用父类(被适配者)的方法
String powerFromSuper = super.supplyPower();
return powerFromSuper + " (通过类适配器转换)";
}
}
```
- **对象适配器**
```java
// 适配器类:电源转换器 (对象适配器模式 - 组合方式)
public class PowerAdapter implements ChinaPowerOutlet {
// 持有适配者对象的引用
private JapanesePowerSource japanesePowerSource;
// 通过构造器传入需要被适配的对象
public PowerAdapter(JapanesePowerSource japanesePowerSource) {
this.japanesePowerSource = japanesePowerSource;
}
// 实现目标接口的方法
@Override
public String providePower() {
// 在内部调用适配者对象的方法,进行转换
String japanesePower = japanesePowerSource.supplyPower();
// 这里可能还有一些适配逻辑,比如电压转换(为了简单,我们只返回结果)
// 实际上,适配器可能还会包含一个物理的变压器来转换电压
return japanesePower + " (通过适配器转换后)";
}
}
```
4. **客户端使用**
```java
public class Traveler {
public static void main(String[] args) {
// 1. 你在日本找到了一个墙壁插座(现有的、不兼容的组件)
JapanesePowerSource japaneseOutlet = new JapanesePowerSource();
// 2. 你把电源转换器(适配器)插到日本插座上
// 并将日本插座“适配”成了中国插座
ChinaPowerOutlet adapter = new PowerAdapter(japaneseOutlet);
// 3. 现在,你可以把你的中国插头充电器插入这个适配器了!
// 对于你的充电器来说,它感觉就像插在中国的插座上一样。
chargeMyPhone(adapter);
}
// 你的手机充电方法,它只认识中国的电源接口
public static void chargeMyPhone(ChinaPowerOutlet outlet) {
String power = outlet.providePower();
System.out.println("手机正在充电,电源信息:" + power);
}
}
```
5. **输出结果**
```txt
手机正在充电,电源信息:100V 交流电 (通过适配器转换后)
```
**最关键的理解点:**
- **适配器** 必须 **实现/继承** **目标接口**,这样客户端才能以统一的方式使用它。
- **适配器** 内部必须 **知道** **适配者**(通过组合或继承),这样才能把客户端的请求**委托**给适配者去真正执行。
这样一来,客户端就完全不需要知道`JapanesePowerSource`的存在,它只和熟悉的`ChinaPowerOutlet`打交道,实现了**解耦**。这就是适配器模式的魅力所在。
逻辑链路:
新系统接口 ← 适配器类(实现) ← 引用老接口 ← 转换调用
用适配器类去实现新接口,然后在适配器类里面引用老的接口,在里面去将老的接口转换成新的接口
3.3 使用场景
-
. 系统升级兼容
- 新系统要用老系统的功能
- 但老系统的接口不符合新标准
- 例子:新日志框架兼容老日志代码
-
集成第三方库
-
项目要用多个第三方库
-
它们功能相似但接口不同
-
例子:同时接入支付宝、微信支付,但接口不一样
-
-
复用遗留代码
-
老系统代码还能用,但接口太老旧
-
不想重写,只想包装一下
-
例子:10 年前的用户服务类,现在还想用
-
-
统一接口标准
-
多个类似组件接口不统一
-
想让他们提供一致的调用方式
-
例子:不同格式的数据转换器 (XML、JSON、CSV)
-
核心价值:
- 不改老代码,让新系统能用老功能,像个"转接头"一样工作。
- 简单说就是:老代码能用但接口不对,适配器来当翻译官。
3.4 场景代码举例
3.4.1 案例1
场景分析
- 4.0系统接口和5.0系统接口:功能相同但参数、实现不同
- 集成平台:想要统一的鉴权 + 统一入参
- 目标:用一套代码调用两个不同版本的接口
代码举例
-
. 定义统一的目标接口
// 集成平台统一的接口标准 public interface UnifiedApi { ApiResponse execute(ApiRequest request); } // 统一请求参数 public class ApiRequest { private String token; // 统一鉴权token private String method; // 方法名:getUser、createOrder等 private Map<String, Object> params; // 统一参数格式 private String version; // 指定调用哪个版本:"4.0" 或 "5.0" // 构造方法、getter、setter... } // 统一响应格式 public class ApiResponse { private boolean success; private String message; private Object data; private String version; // 标识来自哪个版本 // 构造方法、getter、setter... } -
现有的两个系统接口(不兼容的)
// 4.0系统接口 - 老系统 public class System4Api { // 4.0系统的鉴权和调用方式 public Map<String, Object> callV4(String authKey, String action, Map<String, String> inputParams) { // 4.0系统的具体实现 Map<String, Object> result = new HashMap<>(); result.put("code", 200); result.put("msg", "4.0系统响应"); result.put("v4_data", "处理结果: " + inputParams); return result; } } // 5.0系统接口 - 新系统 public class System5Api { // 5.0系统的鉴权和调用方式(完全不同) public System5Response invokeV5(System5Request request) { // 5.0系统的具体实现 System5Response response = new System5Response(); response.setStatus(0); response.setMessage("5.0系统响应"); response.setPayload("处理结果: " + request.getParameters()); return response; } } // 5.0系统的请求类 class System5Request { private String accessToken; private String operation; private Map<String, Object> parameters; // getter、setter... } // 5.0系统的响应类 class System5Response { private int status; private String message; private Object payload; // getter、setter... } -
创建适配器(核心)
// 4.0系统适配器 public class System4Adapter implements UnifiedApi { private System4Api system4Api; public System4Adapter(System4Api system4Api) { this.system4Api = system4Api; } @Override public ApiResponse execute(ApiRequest request) { // 统一的鉴权验证 if (!authenticate(request.getToken())) { return ApiResponse.fail("鉴权失败"); } // 将统一参数转换成4.0系统需要的格式 Map<String, String> v4Params = convertToV4Params(request.getParams()); // 调用4.0系统 Map<String, Object> v4Result = system4Api.callV4( extractV4AuthKey(request.getToken()), // 从统一token提取4.0的authKey request.getMethod(), v4Params ); // 将4.0系统的响应转换成统一格式 return convertToUnifiedResponse(v4Result, "4.0"); } private Map<String, String> convertToV4Params(Map<String, Object> unifiedParams) { Map<String, String> v4Params = new HashMap<>(); for (Map.Entry<String, Object> entry : unifiedParams.entrySet()) { v4Params.put(entry.getKey(), String.valueOf(entry.getValue())); } return v4Params; } private ApiResponse convertToUnifiedResponse(Map<String, Object> v4Result, String version) { ApiResponse response = new ApiResponse(); response.setSuccess("200".equals(String.valueOf(v4Result.get("code")))); response.setMessage(String.valueOf(v4Result.get("msg"))); response.setData(v4Result.get("v4_data")); response.setVersion(version); return response; } private boolean authenticate(String token) { // 统一的鉴权逻辑 return token != null && token.startsWith("auth_"); } private String extractV4AuthKey(String token) { // 从统一token中提取4.0系统需要的authKey return token.replace("auth_", "v4_key_"); } } // 5.0系统适配器 public class System5Adapter implements UnifiedApi { private System5Api system5Api; public System5Adapter(System5Api system5Api) { this.system5Api = system5Api; } @Override public ApiResponse execute(ApiRequest request) { // 统一的鉴权验证(和4.0适配器一样) if (!authenticate(request.getToken())) { return ApiResponse.fail("鉴权失败"); } // 将统一参数转换成5.0系统需要的格式 System5Request v5Request = new System5Request(); v5Request.setAccessToken(extractV5Token(request.getToken())); v5Request.setOperation(request.getMethod()); v5Request.setParameters(request.getParams()); // 调用5.0系统 System5Response v5Response = system5Api.invokeV5(v5Request); // 将5.0系统的响应转换成统一格式 return convertToUnifiedResponse(v5Response, "5.0"); } private ApiResponse convertToUnifiedResponse(System5Response v5Response, String version) { ApiResponse response = new ApiResponse(); response.setSuccess(v5Response.getStatus() == 0); response.setMessage(v5Response.getMessage()); response.setData(v5Response.getPayload()); response.setVersion(version); return response; } private boolean authenticate(String token) { // 统一的鉴权逻辑 return token != null && token.startsWith("auth_"); } private String extractV5Token(String token) { // 从统一token中提取5.0系统需要的accessToken return token.replace("auth_", "v5_access_"); } } -
集成平台的使用
// 集成平台服务 public class IntegrationPlatform { private Map<String, UnifiedApi> adapters = new HashMap<>(); public IntegrationPlatform() { // 初始化所有适配器 adapters.put("4.0", new System4Adapter(new System4Api())); adapters.put("5.0", new System5Adapter(new System5Api())); } public ApiResponse callSystem(ApiRequest request) { // 根据版本选择对应的适配器 UnifiedApi adapter = adapters.get(request.getVersion()); if (adapter == null) { return ApiResponse.fail("不支持的版本: " + request.getVersion()); } // 统一调用 - 平台代码完全不用关心底层是4.0还是5.0 return adapter.execute(request); } } -
Client
public class Client { public static void main(String[] args) { IntegrationPlatform platform = new IntegrationPlatform(); // 准备统一参数 Map<String, Object> params = new HashMap<>(); params.put("userId", 123); params.put("userName", "张三"); ApiRequest request = new ApiRequest(); request.setToken("auth_123456789"); request.setMethod("getUser"); request.setParams(params); // 调用4.0系统 request.setVersion("4.0"); ApiResponse response4 = platform.callSystem(request); System.out.println("4.0响应: " + response4.getMessage()); // 调用5.0系统(同一套参数和鉴权) request.setVersion("5.0"); ApiResponse response5 = platform.callSystem(request); System.out.println("5.0响应: " + response5.getMessage()); } }
3.4.2 案例2
场景分析
- 底层有两套支付系统:支付宝,微信
- 目标:想要让两套支付系统请求参数和返回参数保持统一
代码实现:
-
定义统一的目标接口
定义一个想要的统一入参和返回参数接口
public interface PayMent { BigDecimal pay(BigDecimal price); } -
支付宝支付系统
这套是支付宝系统接口需要的请求方法
public class AaliPay { public Double aliPay(double price){ System.out.println("支付宝支付:"+price); return price; } } -
微信支付系统
这套是微信支付系统的支付方法
public class WeChatPay { public String pay(String price){ System.out.println("微信支付"+price); return "100"; } } -
支付宝适配器
public class AaliPayAdapter implements PayMent{ private AaliPay aliPay; public AaliPayAdapter(AaliPay aliPay){ this.aliPay=aliPay; } @Override public BigDecimal pay(BigDecimal price) { Double res = aliPay.aliPay(price.doubleValue()); System.out.println("支付宝支付转换器"); return new BigDecimal(res); } } -
微信适配器
public class WeChatPayAdapter implements PayMent{ private WeChatPay weChatPay; public WeChatPayAdapter(WeChatPay weChatPay) { this.weChatPay = weChatPay; } @Override public BigDecimal pay(BigDecimal price) { String payRes = weChatPay.pay(price.toPlainString()); System.out.println("微信支付系统转换器"); return new BigDecimal(payRes); } } -
支付工厂方法
public class PayMentFactory { public static PayMent getPayment(PaymentEnums paymentEnums){ switch (paymentEnums){ case Ali: AaliPay aaliPay = new AaliPay(); return new AaliPayAdapter(aaliPay); case WeChat: WeChatPay weChatPay = new WeChatPay(); return new WeChatPayAdapter(weChatPay); default: throw new RuntimeException("不支持类型"); } } } -
支付系统枚举
@Getter @AllArgsConstructor public enum PaymentEnums { WeChat(1,"微信"), Ali(2,"支付宝"); private final Integer code; private final String desc; } -
测试类
public class AdapterClient { public static void main(String[] args) { PayMent aliPayMent = PayMentFactory.getPayment(PaymentEnums.Ali); PayMent webChatPayMent = PayMentFactory.getPayment(PaymentEnums.WeChat); BigDecimal aliPay = aliPayMent.pay(new BigDecimal("100")); BigDecimal webChatPay = webChatPayMent.pay(new BigDecimal("100")); } }
本案例使用了工厂方法模式 + 适配器模式;主要清楚一点,对于每种设计模式不要单一孤立的看,应该是:关于创建对象的想想建造者模式,关于结构的想想结构性模式,关于类的行为的去用行为模式;对于类的每个行动将其划分出来,然后使用对应的设计模式,就是组合使用设计模式了。
他们的职责划分是有意义的:
- 创建型模式(解决对象创建问题)
- 结构型模式(解决类和对象组合问题)
- 行为型模式(解决对象间通信问题)
四,装饰者模式
核心思想:在不修改原类代码的前提下,动态地为对象添加新的功能。
4.1 装饰器模式的结构
装饰器模式主要由以下 四个角色 组成:
Component(抽象组件)
│
├── ConcreteComponent(具体组件)
│
└── Decorator(抽象装饰类)
│
├── ConcreteDecoratorA(具体装饰类A)
└── ConcreteDecoratorB(具体装饰类B)
4.2 代码实现(接口版)
UML类图
┌───────────────────────┐
│ Coffee (接口) │
│ + getDescription() │
│ + getCost() │
└───────────┬───────────┘
│
┌─────────────┴──────────────────┐
│ │
┌──────────────┐ ┌────────────────────────┐
│ SimpleCoffee │ │ CoffeeDecorator(抽象类)│
│ implements │ │ implements Coffee │
└──────────────┘ │ - coffee: Coffee │
│ + getDescription() │
│ + getCost() │
└───────────┬─────────────┘
│
┌──────────────────────────┴──────────────────────────┐
│ │
┌──────────────────┐ ┌──────────────────┐
│ MilkDecorator │ │ SugarDecorator │
│ extends Decorator │ │ extends Decorator│
└──────────────────┘ └──────────────────┘
抽象组件
- 定义对象的抽象接口(例如所有功能的公共方法)。
- 是被装饰的对象和装饰器的共同父类或接口。
public interface Coffee {
String getDescription();
double getCost();
}
具体组件
- 实现了抽象组件的接口。
- 是最核心的功能类,即被“装饰”的原始对象。
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple coffee";
}
@Override
public double getCost() {
return 5.0;
}
}
抽象装饰类
- 实现 Component 接口,但内部持有一个 Component 对象的引用。
- 将请求委托给这个被装饰的对象。
- 为子类(具体装饰器)提供扩展点。
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee; // 被装饰对象
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
具体装饰类
- 在继承
Decorator的基础上,添加新的功能。 - 可以在方法前后扩展逻辑。
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", milk";
}
@Override
public double getCost() {
return decoratedCoffee.getCost() + 1.5;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", sugar";
}
@Override
public double getCost() {
return decoratedCoffee.getCost() + 0.5;
}
}
4.3 代码实现(抽象类版)
UML类图
┌──────────────────────┐
│ Coffee (抽象类) │
│ + getDescription() │
│ + getCost() [abstract]│
└───────────┬──────────┘
│
┌────────────┴────────────┐
│ │
┌──────────────┐ ┌────────────────────────┐
│ SimpleCoffee │ │ CoffeeDecorator(抽象类)│
│ extends Coffee│ │ extends Coffee │
└──────────────┘ │ - coffee: Coffee │
│ + getDescription() │
│ + getCost() │
└───────────┬─────────────┘
│
┌────────────┴─────────────┐
│ MilkDecorator extends │
│ CoffeeDecorator │
└───────────────────────────┘
抽象组件
public abstract class Coffee {
public String getDescription() {
return "Unknown coffee";
}
public abstract double getCost();
}
具体组件
public class SimpleCoffee extends Coffee {
@Override
public String getDescription() {
return "Simple coffee";
}
@Override
public double getCost() {
return 5.0;
}
}
抽象类装饰类
public abstract class CoffeeDecorator extends Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double getCost() {
return coffee.getCost();
}
}
具体装饰类
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", milk";
}
@Override
public double getCost() {
return super.getCost() + 1.5;
}
}
4.4 两种实现方式的区别
| 对比项 | 接口版装饰器 | 抽象类版装饰器 |
|---|---|---|
| 继承结构 | 通过接口实现(轻) | 通过继承(重) |
| 代码复用 | 无共享逻辑 | 可共享默认实现 |
| 灵活性 | 高(可多实现) | 较低(单继承) |
| 状态管理 | 无状态 | 可包含字段、默认实现 |
| 使用代表 | 业务类(如 Coffee) | 框架底层(如 InputStream) |
如何选择:
| 如果你…… | 推荐用 |
|---|---|
| 只是想扩展功能、不需要状态 | 接口版 |
| 想提供默认实现、需要共享逻辑 | 抽象类版 |
| 想实现最大灵活性(比如叠加装饰) | 接口版 |
| 要写框架或工具类(像 IO 流、Servlet) | 抽象类版 |
4.5 装饰器模式的作用
- 动态扩展对象的功能,而不是通过继承。
- 让功能的添加变得更加灵活。
- 可以“叠加”多个装饰器,从而形成一个功能链。
例如:
public class Main {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee(); // 基础咖啡
coffee = new MilkDecorator(coffee); // 加牛奶
coffee = new SugarDecorator(coffee); // 加糖
System.out.println(coffee.getDescription()); // 输出:Simple coffee, milk, sugar
System.out.println(coffee.getCost()); // 输出:7.0
}
}
4.6 什么时候使用装饰器模式?
| 使用场景 | 示例 |
|---|---|
| 想在不修改原有类的情况下为对象添加功能 | 不想改第三方库或已有业务代码 |
| 需要动态组合多种功能 | 比如:日志、加密、缓存可以自由叠加 |
| 不想使用继承造成类爆炸 | 每种功能都用继承会导致类数量急剧膨胀 |
4.7 装饰器模式解决的问题
| 问题 | 装饰器解决方式 |
|---|---|
| 子类继承导致类数量暴增 | 改用对象组合的方式 |
| 想在运行时改变行为 | 可动态包装不同装饰器 |
| 修改核心类困难(封闭原则) | 装饰器通过包装扩展行为 |
4.8 在 Java 标准库中的体现
其实 Java 自带了很多装饰器模式的例子,比如:
| 类 | 描述 |
|---|---|
java.io.BufferedInputStream 装饰 InputStream | 为输入流添加缓冲功能 |
java.io.DataInputStream 装饰 InputStream | 添加按类型读取功能 |
java.util.Collections.synchronizedList() | 返回线程安全的装饰版 List |
javax.servlet.http.HttpServletRequestWrapper | 动态增强请求对象 |
4.9 抽象装饰类的作用
CoffeeDecorator 的作用有三个层面:
| 作用类型 | 解释 |
|---|---|
| 类型统一 | 它实现了 Coffee 接口,保证所有子类(装饰器)都能互相叠加(因为类型兼容)。 |
| 复用委托逻辑 | 它封装了公共逻辑,比如 getDescription() 调用 coffee.getDescription()。每个具体装饰器不用重复写委托。 |
| 提供扩展模板 | 它本身是抽象的,留出空位给具体装饰器做增强(开闭原则)。 |
简单一句话:
CoffeeDecorator是装饰器链条的“粘合层”和“模板类”。
它让每个装饰器既是“被装饰者”又是“装饰者”。
举个对比例子
-
没有抽象装饰类(无法嵌套)
public class MilkDecorator implements Coffee { private SimpleCoffee coffee; // 写死类型 ... } MilkDecorator milk = new MilkDecorator(new SimpleCoffee()); // OK SugarDecorator sugar = new SugarDecorator(milk); // 类型错误 -
有抽象装饰类(可无限嵌套)
public abstract class CoffeeDecorator implements Coffee { protected Coffee coffee; public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; } } public class MilkDecorator extends CoffeeDecorator { ... } public class SugarDecorator extends CoffeeDecorator { ... } Coffee c = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
为什么 CoffeeDecorator 必须是“抽象”的?
因为:
- 它只是提供公共的委托逻辑;
- 本身没有实际功能;
- 不打算被直接实例化;
- 所以用
abstract明确表达“只能被继承,不能直接用”。
否则你可以写出奇怪的东西:
Coffee c = new CoffeeDecorator(new SimpleCoffee()); // 没有额外功能的装饰器
这在语义上是毫无意义的。
一句话总结:
CoffeeDecorator是装饰器模式中维持“类型自洽”的关键,它让每个装饰器既实现了统一接口(能被别人装饰),又持有接口实例(能装饰别人)。
没它,就没有“无限叠加”的魔力。
五,桥接模式
5.1 桥接模式是什么
定义: 桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立变化。
通俗理解: 它就是给你搭了一座“桥”,让两个本来紧耦合的维度解耦,从此互不干扰,各自独立扩展。
5.2 为什么需要桥接模式
想象一个需求:
你要开发一个“消息发送系统”,有不同的消息类型(普通消息、紧急消息),
同时支持多种发送方式(短信、邮件、微信...)。
如果你直接用继承来实现,可能会变成这样:
普通短信消息类
普通邮件消息类
紧急短信消息类
紧急邮件消息类
...
如果再加一个“特急消息”类型?类的数量就会 指数级增长。
5.3 桥接模式的核心思想
桥接模式把系统拆成两个 **“维度”**:
| 维度 | 含义 | 举例 |
|---|---|---|
| 抽象层(Abstraction) | 表示消息的类型 | 普通消息、紧急消息 |
| 实现层(Implementor) | 表示发送的方式 | 短信、邮件、微信 |
然后通过“桥接”把两者组合起来:
不再用继承,而是通过 ** 组合(组合一个实现类的引用)** 的方式。
5.4 类图结构
Abstraction(抽象类)
↑
RefinedAbstraction(扩展抽象类)
↓(桥)
Implementor(实现接口)
↑
ConcreteImplementor(具体实现类)
5.5 代码实现
定义实现接口(消息发送方式)
/**
* 消息发送
*/
public interface MessageSender {
// 统一消息发送的行为接口
public void sendMsg(String msg);
}
创建几个具体实现
public class EmailMessageSender implements MessageSender{
@Override
public void sendMsg(String msg) {
System.out.println("邮箱发送");
}
}
public class SmsMessageSender implements MessageSender{
@Override
public void sendMsg(String msg) {
System.out.println("短信消息发送");
}
}
定义抽象层(消息类型)
/**
* 这个抽象层就是核心,如果需要给类扩展维度,就定义一个这个
*/
public abstract class MessageTypeBridge{
//这个就相当于桥,这里不能定义成private
protected MessageSender sender;
public MessageTypeBridge(MessageSender sender) {
this.sender = sender;
}
public abstract void sendMessageByType(String message);
}
扩展抽象类(不同类型的消息)
public class NormalMessageSender extends MessageTypeBridge{
public NormalMessageSender(MessageSender sender) {
super(sender);
}
@Override
public void sendMessageByType(String message) {
System.out.print("[普通消息]:");
sender.sendMsg(message);
}
}
public class UrgenrMessageSender extends MessageTypeBridge{
public UrgenrMessageSender(MessageSender sender) {
super(sender);
}
@Override
public void sendMessageByType(String message) {
System.out.print("[紧急消息]:");
sender.sendMsg(message);
}
}
客户端调用
public class Client {
public static void main(String[] args) {
MessageSender emailSender = new EmailMessageSender();
MessageSender smsSender = new SmsMessageSender();
MessageTypeBridge emailNormalMessageSender = new NormalMessageSender(emailSender);
MessageTypeBridge smsNormalMessageSender = new NormalMessageSender(smsSender);
emailNormalMessageSender.sendMessageByType("测试消息");
smsNormalMessageSender.sendMessageByType("测试消息");
System.out.println("------------------------------");
MessageTypeBridge emailUrgentMessageSender = new UrgenrMessageSender(emailSender);
MessageTypeBridge smsUrgentMessageSender = new UrgenrMessageSender(smsSender);
emailUrgentMessageSender.sendMessageByType("测试消息");
smsUrgentMessageSender.sendMessageByType("测试消息");
}
}
输出结果:
[普通消息]:邮箱发送
[普通消息]:短信消息发送
------------------------------
[紧急消息]:邮箱发送
[紧急消息]:短信消息发送
5.6 桥接模式的优点
降低耦合度:抽象与实现分离,不互相依赖。
提高扩展性:新加消息类型 or 新的发送方式,不需要改动原代码。
符合开闭原则:对扩展开放,对修改关闭。
5.7 什么时候用桥接模式
用在需要同时扩展多个维度的场景:
| 场景 | 示例 |
|---|---|
| 不同操作系统 + 不同文件格式 | Windows、Mac、Linux 各种播放器支持 mp3/mp4/avi |
| 不同数据库 + 不同驱动方式 | MySQL、PostgreSQL 使用不同驱动 |
| 不同消息类型 + 不同发送渠道 | (就像上面我们的例子) |
5.8 总结
桥接模式就是:用组合代替继承,把多维度变化的系统结构化、模块化,让它更灵活、更可扩展
理解:假设我们系统刚开始设计一个消息发送服务,我们这边统一定义了一个接口去管理消息发送的行为;此时消息发送只需要考虑底层实现;等到后续系统扩展了,业务上需要对消息进行类型等区分,这个并不是底层实现,这个相当于抽象层的东西,我们如果按照底层实现的规矩去新写一个消息类型接口,然后底层实现分别去实现消息类型就会导致类的数量 = 消息类型 * 底层实现;如果后续再加就是 其他抽象 * 消息类型 * 底层实现,这样会导致每添加一种抽象,代码的数量就会指数级增长,这个是不现实的;而桥接模式就是为了解决这个问题产生的;我们将抽象和底层实现分开,通过将抽象层的东西归于抽象类中,然后引用实现层从而实现组合而非继承,从而解决这个类爆炸的问题.
六,外观模式
核心思想
- 为子系统中的一组复杂接口提供一个统一的高层接口,使得子系统更容易使用。
- 简单来说:“外部只和我这个‘门面’打交道,内部的复杂逻辑我自己搞定。”
6.1 为什么需要它
假设你现在有一个 开票系统,它内部有一堆子系统:
- 税控系统接口
- 发票模板服务
- 校验签章模块
- 开票记录日志模块
- 通知发送模块(短信、邮件等)
业务调用时必须依次调用这些服务,还要处理异常、日志、事务、回滚等等。
// 没有外观模式的情况下
taxApi.validate();
templateService.load();
invoiceCore.create();
logService.record();
notifyService.send();
每次业务都要写一遍这些步骤,代码臃肿、难维护。
6.2 引入外观模式
我们设计一个外观类:
public class InvoiceFacade {
private TaxApi taxApi;
private TemplateService templateService;
private InvoiceCore invoiceCore;
private LogService logService;
private NotifyService notifyService;
public InvoiceFacade(TaxApi taxApi, TemplateService templateService,
InvoiceCore invoiceCore, LogService logService,
NotifyService notifyService) {
this.taxApi = taxApi;
this.templateService = templateService;
this.invoiceCore = invoiceCore;
this.logService = logService;
this.notifyService = notifyService;
}
public void createInvoice(InvoiceRequest request) {
taxApi.validate(request);
templateService.loadTemplate(request.getTemplateId());
invoiceCore.createInvoice(request);
logService.record("开票成功: " + request.getInvoiceNo());
notifyService.send(request.getUserId(), "发票已开具");
}
}
外部使用时:
// 控制器层只需要这一行
invoiceFacade.createInvoice(request);
控制层(Controller)不再关心内部调用多少个模块。
6.3 外观模式的 核心价值
| 作用 | 描述 |
|---|---|
| 简化调用 | 外部系统只调用一个类 |
| 屏蔽复杂性 | 内部逻辑随便改,不影响外部 |
| 更易维护 | 修改子系统不需要改业务层 |
| 代码解耦 | 业务 → 外观层 → 子系统,各层职责清晰 |
七,组合模式
7.1 核心思想
组合模式(Composite Pattern): 让“单个对象(叶子节点)”与“组合对象(容器节点)”在使用上具有一致性。
通俗讲就是:“我希望单个元素和一组元素都能被统一处理。”
7.2 为什么要有组合模式
想象一个文件系统:
- 有 文件(File)
- 也有 文件夹(Folder)
- 文件夹里还能再包含文件或文件夹
你希望能写出这样统一的代码:
rootFolder.showInfo();
系统能自动递归打印出整个目录树,不需要区分“是文件”还是“文件夹”,这就是组合模式的威力。
7.3 UML 结构图
┌──────────────────────────┐
│ Component │ <─── 抽象组件(定义通用行为)
│ + showInfo() │
└──────────┬───────────────┘
│
┌────────────┴───────────────┐
│ │
┌────────────┐ ┌──────────────────┐
│ Leaf │ │ Composite │
│ 文件节点 │ │ 文件夹节点 │
│ + showInfo()│ │ + add(Component)│
│ │ │ + showInfo() │
└────────────┘ └──────────────────┘
7.4 Java 实现示例
抽象组件
// 抽象组件
interface FileComponent {
void showInfo(String indent);
}
叶子节点(文件)
// 叶子节点(文件)
public class FileLeaf implements FileComponent {
private String name;
public FileLeaf(String name) {
this.name = name;
}
@Override
public void showInfo(String indent) {
System.out.println(indent + " 文件:" + name);
}
}
容器节点(文件夹)
// 容器节点(文件夹)
class FolderComposite implements FileComponent {
private String name;
private java.util.List<FileComponent> children = new java.util.ArrayList<>();
public FolderComposite(String name) {
this.name = name;
}
public void add(FileComponent component) {
children.add(component);
}
@Override
public void showInfo(String indent) {
System.out.println(indent + " 文件夹:" + name);
for (FileComponent child : children) {
child.showInfo(indent + " ");
}
}
}
测试
// 测试
public class CompositeDemo {
public static void main(String[] args) {
// 创建文件和文件夹结构
FileLeaf file1 = new FileLeaf("设计模式笔记.txt");
FileLeaf file2 = new FileLeaf("外观模式.png");
FolderComposite folder1 = new FolderComposite("结构型模式");
folder1.add(file1);
folder1.add(file2);
FolderComposite root = new FolderComposite("学习笔记");
root.add(folder1);
root.add(new FileLeaf("README.md"));
// 一行调用,递归展示结构
root.showInfo("");
}
}
输出结果:
文件夹:学习笔记
文件夹:结构型模式
文件:设计模式笔记.txt
文件:外观模式.png
文件:README.md
7.5 应用场景
“当你的对象形成一种树形层级结构(组织、菜单、文件、UI 组件、权限体系等),并且想让外部代码不关心它是单个元素还是一组元素时,就该用组合模式。”
-
文件系统
-
结构:文件夹内可以包含文件或文件夹。
-
操作:对文件和文件夹都能执行如“删除”“复制”“解压”等相同行为。
-
实际例子:Windows 的资源管理器、Linux 的目录结构。
同样的 delete() 方法,既能删除单个文件,也能删除整个文件夹。
-
-
菜单系统
-
结构:一个菜单(Menu)可以包含子菜单(SubMenu)或菜单项(MenuItem)
菜单 ├── 文件 │ ├── 新建 │ ├── 打开 │ └── 退出 ├── 编辑 │ ├── 撤销 │ └── 粘贴 └── 帮助 └── 关于 -
操作:用户点击操作都是一样的行为(如显示、跳转)
menu.render(); // 不管它是菜单项还是子菜单,都能统一显示
-
-
组织结构
- **结构**:部门里有子部门,也有员工。
- **行为**:计算总薪资、发送通知、绩效考核……
- 无论是部门还是员工,都有 `getSalary()` 或 `notifyMessage()` 方法。
```java
interface OrgUnit {
void notifyMessage(String msg);
}
class Employee implements OrgUnit { ... }
class Department implements OrgUnit {
List<OrgUnit> members;
...
}
```
4. 权限 / 菜单树
在权限系统中,每个节点可以是:
- 功能模块(父节点)
- 具体操作按钮(子节点)
对外暴露统一的接口,不用关心是“**功能**”还是“**具体按钮**”。
```java
permission.check(user);
```
总结
| 关键特征 | 是否适合组合模式 |
|---|---|
| 对象存在层级结构(树状) | 是 |
| 需要对单个对象和组合对象统一处理 | 是 |
| 操作具有递归性(如执行、渲染、计算) | 是 |
| 组合中元素类型相同或相似 | 是 |
| 对象间依赖较弱,不需要复杂交互 | 是 |
八,享元模式
8.1 享元模式核心思想
享元模式(Flyweight Pattern): 通过共享对象来减少系统中对象的数量,以节省内存和提高性能。
通俗理解:“如果很多对象长得一样,就不要重复创建,用同一个对象就够了。”
关键点:
- 共享相同状态(内部状态)
- 把变化的状态(外部状态)通过参数传入
- 通过工厂来管理共享对象
8.2 为什么要用享元模式
当系统中需要创建大量对象,且这些对象大部分内容相同时,如果每次都 new 一个对象,会占用大量内存。
比如:
- 文本编辑器中的字符对象
- 游戏中的棋子、子弹
- UI 中的重复图标
如果每个都创建一个对象 → 内存占用很大,如果共享相同对象 → 内存节省很多
8.3 享元模式结构
+-----------------+
| Flyweight | <-- 抽象享元接口
| + operation(extrinsicState)
+-----------------+
^
|
+-----------------+ +-----------------+
| ConcreteFlyweight | | UnsharedFlyweight (可选)
+-----------------+ +-----------------+
| + intrinsicState| | + 不共享状态 |
+-----------------+ +-----------------+
^
|
+-----------------+
| FlyweightFactory |
+-----------------+
| + getFlyweight()|
+-----------------+
- Intrinsic State(内部状态):可共享的,不会随外部改变
- Extrinsic State(外部状态):随使用场景变化,由外部传入
8.4 代码实现
享元接口
// 享元接口
public interface Icon {
void draw(IconRenderContext context);
}
具体享元
// 具体享元类(内部状态:图标文件路径)
public class ConcreteIcon implements Icon {
private final String iconPath;
public ConcreteIcon(String iconPath) {
this.iconPath = iconPath;
// 模拟加载图标资源
System.out.println("加载图标资源: " + iconPath);
}
@Override
public void draw(IconRenderContext context) {
// 使用外部状态(位置、透明度)
System.out.printf("绘制图标 [%s] at (%d,%d), size=(%dx%d), alpha=%.1f%n",
iconPath, context.x, context.y, context.width, context.height, context.alpha);
}
}
外部状态
// 外部状态:图标显示参数(位置/大小/透明度)
public class IconRenderContext {
int x, y;
int width, height;
float alpha;
public IconRenderContext(int x, int y, int width, int height, float alpha) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.alpha = alpha;
}
}
享元工厂
// 享元工厂(缓存共享图标)
public class IconFactory {
private final Map<String, Icon> iconPool = new ConcurrentHashMap<>();
private static final IconFactory INSTANCE = new IconFactory();
private IconFactory() {}
public static IconFactory getInstance() { return INSTANCE; }
public Icon getIcon(String path) {
return iconPool.computeIfAbsent(path, ConcreteIcon::new);
}
public int getPoolSize() {
return iconPool.size();
}
}
测试
// 使用示例
public class FlyweightIconDemo {
public static void main(String[] args) {
IconFactory factory = IconFactory.getInstance();
// 多次获取相同图标(只加载一次)
Icon userIcon1 = factory.getIcon("icons/user.png");
Icon userIcon2 = factory.getIcon("icons/user.png");
Icon folderIcon = factory.getIcon("icons/folder.png");
// 绘制时传入不同外部状态
userIcon1.draw(new IconRenderContext(10, 10, 32, 32, 1.0f));
userIcon2.draw(new IconRenderContext(100, 50, 64, 64, 0.8f));
folderIcon.draw(new IconRenderContext(200, 20, 48, 48, 1.0f));
System.out.println("图标池中图标数量:" + factory.getPoolSize());
}
}
输出示例:
加载图标资源: icons/user.png
加载图标资源: icons/folder.png
绘制图标 [icons/user.png] at (10,10), size=(32x32), alpha=1.0
绘制图标 [icons/user.png] at (100,50), size=(64x64), alpha=0.8
绘制图标 [icons/folder.png] at (200,20), size=(48x48), alpha=1.0
图标池中图标数量:2
8.5 应用场景总结
-
业务层(几乎不用)
在常规的企业业务系统里,比如:用户管理、订单、开票、库存、报表……;这些都是数据驱动的业务,不会频繁 new 上万个逻辑相同的对象。
所以享元模式在这层没太大用处,因为:
- 节省的内存有限
- 复杂度反而上升
- Spring 本身就有 Bean 复用机制(单例 Bean)
简单来说:得不偿失。
-
框架层(非常常见)
在框架里就不同了。框架通常要频繁创建和管理对象,性能与内存消耗敏感。
典型例子 :
框架 享元思想体现 说明 Spring 单例 Bean、@Scope("singleton") 每个 Bean 只实例化一次,全局共享 MyBatis Mapper 接口代理缓存 每个 Mapper 对应的代理类在内部缓存复用 JDBC 连接池 Connection 复用 池化思想,本质就是享元:同类资源共享 Netty ByteBuf 池、ChannelHandler 共享 高性能网络通信中频繁复用对象 ThreadPoolExecutor Worker 线程复用 多线程池中线程作为享元对象共享执行任务 这些都是享元思想的变体:控制对象创建 + 管理共享资源。
-
性能敏感系统
如果是图形系统、游戏、仿真系统、监控系统等,就会频繁用到享元。
比如:
场景 示例 游戏 棋盘格、子弹、怪物模型(重复太多) GIS 系统 地图瓦片对象共享 大屏监控 数据点共享样式、节点缓存 报表系统 单元格模板共享、字体样式共享 这些场景下,享元模式能显著减少内存占用。
总结一句话:
- 业务层:几乎不用(除非极端性能要求)
- 框架层:大量使用(对象池、单例、Bean 缓存等)
- 底层系统:频繁使用(图形、游戏、网络通信)
8.6 总结
享元模式就是通过工厂控制对象的创建,使得相同的对象只被创建一次,并在不同场景中共享使用。
小结:
- 享元模式 ≈ 对象池 + 内部状态共享
- 用法和组合模式、桥接模式不同,它的核心关注点是节省内存和对象复用
- 在非 Spring 项目中 —— 享元模式需要手动写工厂 + 缓存;
- 在 Spring 项目中 —— Spring IoC 容器本身就是一个天然的享元工厂。