写在前面
Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!
代理模式
定义
结构型设计模式, 为一个对象提供一个替身或占位符, 进而控制对这个对象的访问。
通过引入一个代理对象, 将客户端对真实对象的直接访问进行封装和控制, 从而在不改变真实对象的情况下, 增加额外的功能或限制。
角色
- 抽象主题: 定义了真实主题和代理主题的共同接口, 使得在任何可以使用真实主题的地方都可以使用代理主题。
- 真实主题: 定义了代理所代表的真实对象, 它包含了业务逻辑, 是代理模式最终要访问的对象。
- 代理主题: 持有对真实主题的引用, 并实现与真实主题相同的接口, 代理对象可以在将请求转发给真实主题之前或之后, 执行一些预处理或 后处理的逻辑。
使用时主要思考点
- 明确代理目的: 在引入代理模式前, 首先要明确代理的目的是什么, 如控制访问(权限控制、远程代理等)、增强功能(如 日志、缓存、事务), 不同的目的可能导致代理的实现方式和复杂程度不同。
- 接口一致性: 代理对象和真实对象必须实现相同的接口。
- 性能开销: 引入代理会增加一层间接性, 这可能会带来一定的性能开销, 在对性能要求极高的场景下, 需要仔细评估。
- 避免过度设计: 一些简单功能可以通过直接调用实现, 引入代理会使系统变得过于复杂。
JDK 动态代理
JDK动态代理是Java语言自动的代理机制, 基于接口实现。这意味着只有实现了接口的类才能使用JDK动态代理。
核心接口
InvocationHandler
接口: 代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。当代理实例上调用方法时, 方法调用将被编码并分派到其调用处理程序的invoke
方法。Proxy
类: 提供了创建动态代理类和实例的静态方法。
代码示例
抽象主题
arduino
public interface UserService {
void registerUser(String username, String password);
void login(String username, String password);
}
真实主题
csharp
public class UserServiceImpl implements UserService {
@Override
public void registerUser(String username, String password) {
System.out.println("开始执行用户注册业务逻辑: " + username);
if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) {
System.out.println("用户 " + username + " 注册成功!");
}
else {
System.out.println("用户注册失败: 用户名或密码不能为空。");
}
System.out.println("用户注册业务逻辑执行完毕。");
}
@Override
public void login(String username, String password) {
System.out.println("开始执行用户登陆业务逻辑: " + username);
if ("admin".equals(username) && "123".equals(password)) {
System.out.println("用户 " + username + " 登录成功!");
}
else {
System.out.println("用户 " + username + " 登录失败: 用户名或密码错误。");
}
System.out.println("用户登录业务逻辑执行完毕。");
}
}
动态代理处理器
typescript
public class PerformanceLogInvocationHandler implements InvocationHandler {
private Object target;
public PerformanceLogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("\n--- 动态代理前置处理: 方法 " + method.getName() + "开始执行, 参数: " + Arrays.toString(args) + " ---");
Object result = null;
try {
// 调用真实对象的方法
result = method.invoke(target, args);
} catch (Exception e) {
System.out.println("动态代理异常处理: 方法 " + method.getName() + " 执行异常: " + e.getMessage());
throw e.getCause();
} finally {
long endTime = System.currentTimeMillis();
System.out.println("--- 动态代理后置处理: 方法 " + method.getName() + " 执行完毕, 耗时: " + (endTime - startTime) + " 毫秒 ---");
System.out.println("操作日志: 方法 " + method.getName() + " 在 " + new Date() + " 被调用。");
}
return result;
}
}
测试类
csharp
public class JdkDynamicProxyTest {
@Test
public void test_jdkProxy() {
System.out.println("--- 使用 JDK 动态代理进行用户服务操作 ---");
UserService realUserService = new UserServiceImpl();
Class<? extends UserService> aClass = realUserService.getClass();
UserService proxyUserService = (UserService) Proxy.newProxyInstance(
aClass.getClassLoader(), // 类加载器
aClass.getInterfaces(), // 真实对象实现的接口器
new PerformanceLogInvocationHandler(realUserService) // 调用处理器
);
System.out.println("\n--- 第一次操作: 注册用户 ---");
proxyUserService.registerUser("赵六", "789012");
System.out.println("\n--- 第二次操作: 登录用户 ---");
proxyUserService.login("admin", "123");
System.out.println("\n--- 第三次操作: 登录失败 ---");
proxyUserService.login("guest", "wrongpass");
}
}
运行结果
lua
--- 使用 JDK 动态代理进行用户服务操作 ---
--- 第一次操作: 注册用户 ---
--- 动态代理前置处理: 方法 registerUser开始执行, 参数: [赵六, 789012] ---
开始执行用户注册业务逻辑: 赵六
用户 赵六 注册成功!
用户注册业务逻辑执行完毕。
--- 动态代理后置处理: 方法 registerUser 执行完毕, 耗时: 0 毫秒 ---
操作日志: 方法 registerUser 在 Mon Sep 29 10:35:32 CST 2025 被调用。
--- 第二次操作: 登录用户 ---
--- 动态代理前置处理: 方法 login开始执行, 参数: [admin, 123] ---
开始执行用户登陆业务逻辑: admin
用户 admin 登录成功!
用户登录业务逻辑执行完毕。
--- 动态代理后置处理: 方法 login 执行完毕, 耗时: 0 毫秒 ---
操作日志: 方法 login 在 Mon Sep 29 10:35:32 CST 2025 被调用。
--- 第三次操作: 登录失败 ---
--- 动态代理前置处理: 方法 login开始执行, 参数: [guest, wrongpass] ---
开始执行用户登陆业务逻辑: guest
用户 guest 登录失败: 用户名或密码错误。
用户登录业务逻辑执行完毕。
--- 动态代理后置处理: 方法 login 执行完毕, 耗时: 0 毫秒 ---
操作日志: 方法 login 在 Mon Sep 29 10:35:32 CST 2025 被调用。
Process finished with exit code 0
CGLIB 动态代理
CGLIB是一个字节码生成库, 它可以在运行时扩展 Java类和实现 Java 接口。CGLIB通过继承方式实现代理, 它会生成一个真实对象的子类作为代理类, 因此可以代理没有实现接口的类。
核心接口
MethodInterceptor
: 接口通过唯一的 intercept方法实现对代理对象方法调用的拦截。核心是通过 MethodProxy.invokeSuper()调用目标类的原始方法,并在前后插入自定义逻辑。
注意事项
- 必须通过
proxy.invokeSuper(obj, args)
调用目标类的原始方法,method.invoke(obj,args)
会使对象再次触发连接器。
代码示例
真实主题
vbnet
public class ConfigurationService {
private Map<String, String> configCache = new HashMap<>();
public String getConfig(String key) {
System.out.println("正在从数据源加载配置项: " + key + "...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
String value = "配置值_" + key + "_" + System.currentTimeMillis();
System.out.println("加载完成, 配置项 " + key + " 的值为: " + value);
return value;
}
public void updateConfig(String key, String value) {
System.out.println("正在更新配置项: " + key + " 为 " + value + "...");
System.out.println("配置项 " + key + " 更新成功。");
}
}
CGLIB 动态代理拦截器
vbnet
public class CacheAndLogMethodInterceptor implements MethodInterceptor {
private Object target;
private Map<String, Object> cache = new HashMap<>();
public CacheAndLogMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("\n--- CGLIB 代理前置处理: 方法 " + method.getName() + " 被调用, 参数: " + Arrays.toString(args) + " ---");
if (method.getName().equals("getConfig") && args.length == 1 && args[0] instanceof String) {
String key = (String) args[0];
if (cache.containsKey(key)) {
System.out.println("CGLIB 代理: 从缓存中获取配置项: " + key);
return cache.get(key);
}
}
Object result = null;
try {
result = proxy.invokeSuper(obj, args);
if (method.getName().equals("getConfig") && args.length == 1 && args[0] instanceof String && result != null) {
String key = (String) args[0];
cache.put(key, result);
System.out.println("CGLIB 代理: 将配置项 " + key + " 放入缓存。");
}
} catch (Exception e) {
System.out.println("CGLIB 代理异常处理: 方法 " + method.getName() + " 执行异常: " + e.getMessage());
throw e.getCause();
} finally {
System.out.println("--- CGLIB 代理后置处理: 方法 " + method.getName() + " 执行完毕。---");
}
return result;
}
}
测试类
csharp
public class CglibDynamicProxyTest {
@Test
public void test_cglib() {
System.out.println("--- 使用 CGLIB 动态代理进行配置服务操作 ---");
ConfigurationService realConfigService = new ConfigurationService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ConfigurationService.class); // 设置父类 即代理的类
enhancer.setCallback(new CacheAndLogMethodInterceptor(realConfigService)); // 设置回调函数
ConfigurationService proxyConfigService = (ConfigurationService) enhancer.create(); // 创建代理对象
System.out.println("\n--- 第一次获取配置: config_key_A ---");
proxyConfigService.getConfig("config_key_A");
System.out.println("\n--- 第二次获取配置: config_key_A(从缓存中获取) ---");
proxyConfigService.getConfig("config_key_A");
System.out.println("\n--- 第三次获取配置: config_key_B ---");
proxyConfigService.getConfig("config_key_B");
System.out.println("\n--- 第四次更新配置: config_key_A ---");
proxyConfigService.updateConfig("config_key_A", "new_value_A");
System.out.println("\n--- 第五次获取配置: config_key_A(更新后应重新加载) ---");
proxyConfigService.getConfig("config_key_A");
}
}
运行结果
lua
--- 使用 CGLIB 动态代理进行配置服务操作 ---
--- 第一次获取配置: config_key_A ---
--- CGLIB 代理前置处理: 方法 getConfig 被调用, 参数: [config_key_A] ---
正在从数据源加载配置项: config_key_A...
加载完成, 配置项 config_key_A 的值为: 配置值_config_key_A_1759113258603
CGLIB 代理: 将配置项 config_key_A 放入缓存。
--- CGLIB 代理后置处理: 方法 getConfig 执行完毕。---
--- 第二次获取配置: config_key_A(从缓存中获取) ---
--- CGLIB 代理前置处理: 方法 getConfig 被调用, 参数: [config_key_A] ---
CGLIB 代理: 从缓存中获取配置项: config_key_A
--- 第三次获取配置: config_key_B ---
--- CGLIB 代理前置处理: 方法 getConfig 被调用, 参数: [config_key_B] ---
正在从数据源加载配置项: config_key_B...
加载完成, 配置项 config_key_B 的值为: 配置值_config_key_B_1759113258715
CGLIB 代理: 将配置项 config_key_B 放入缓存。
--- CGLIB 代理后置处理: 方法 getConfig 执行完毕。---
--- 第四次更新配置: config_key_A ---
--- CGLIB 代理前置处理: 方法 updateConfig 被调用, 参数: [config_key_A, new_value_A] ---
正在更新配置项: config_key_A 为 new_value_A...
配置项 config_key_A 更新成功。
--- CGLIB 代理后置处理: 方法 updateConfig 执行完毕。---
--- 第五次获取配置: config_key_A(更新后应重新加载) ---
--- CGLIB 代理前置处理: 方法 getConfig 被调用, 参数: [config_key_A] ---
CGLIB 代理: 从缓存中获取配置项: config_key_A
Process finished with exit code 0
代理模式+装饰器模式
装饰器模式
结构型设计模式, 允许在不改变原有对象结构的情况下, 动态地给对象添加新的功能。
通过创建一个包装对象, 来包裹真实的对象, 并在不改变其接口的前提下增强其功能。
案例
假设正在开发一个文件管理系统, 用户可以对文件进行读写操作。对于不同的文件或不同的用户, 我们可能需要动态地组合多种文件操作功能。例如:
- 权限控制: 只有特定用户才能访问某些文件。
- 数据加密: 敏感文件在写入时需要加密, 读取时需要解密。
- 数据压缩: 大文件在传输或存储时需要压缩, 读取时需要解压。
模式职责
- 代理模式: 作为文件操作的入口, 提供基础的访问控制或预处理, 例如检查文件是否存在、用户是否有权限进行操作等, 确保了文件操作的合法性。
- 装饰器模式: 动态地添加额外的功能, 如加密、解密、压缩、解压缩等。
代码示例
抽象组件/抽象主题
arduino
public interface FileOperation {
void write(String filePath, String content);
String read(String filePath);
}
- 定义了文件读写操作的抽象, 是代理和装饰器模式共同的接口, 确保了它们可以相互兼容和组合。
具体组件/真实主题
typescript
public class RealFileOperation implements FileOperation {
private String fileContent = "";
@Override
public void write(String filePath, String content) {
System.out.println("\n[真实文件操作] 正在将内容写入文件: " + filePath);
this.fileContent = content;
System.out.println("写入成功, 文件内容为: " + content);
}
@Override
public String read(String filePath) {
System.out.println("\n[真实文件操作] 正在从文件读取内容: " + filePath);
System.out.println("读取成功, 文件内容为: " + fileContent);
return fileContent;
}
}
- 实现了
FileOperation
接口, 负责模拟实际的文件读写操作, 不包含任何权限、加密或压缩逻辑。
代理主题
typescript
public class FileAccessProxy implements FileOperation {
/**
* 持有实际操作文件类的引用
*/
private RealFileOperation realFileOperation;
private String currentUser;
public FileAccessProxy(String currentUser) {
this.realFileOperation = new RealFileOperation();
this.currentUser = currentUser;
}
private boolean checkPermission(String filePath, String operationType) {
if (filePath.contains("sensitive") && "write".equals(operationType) && !"admin".equals(currentUser)) {
System.out.println("[文件访问代理] 权限不足: 用户 " + currentUser + " 无权写入敏感文件: " + filePath);
return false;
}
System.out.println("[文件访问代理] 权限检查通过: 用户 " + currentUser + " 可以执行 " + operationType + " 操作。");
return true;
}
@Override
public void write(String filePath, String content) {
if (this.checkPermission(filePath, "write")) {
realFileOperation.write(filePath, content);
}
}
@Override
public String read(String filePath) {
if (this.checkPermission(filePath, "read")) {
return realFileOperation.read(filePath);
}
return "";
}
}
- 实现了
FileOperation
接口, 并持有一个RealFileOperation
实例。主要职责是在调用真实文件操作之前, 进行用户权限的检查。如果权限不足则阻止操作;否则,将请求转发给真实文件操作类。
抽象装饰器类
typescript
public abstract class FileOperationDecorator implements FileOperation {
protected FileOperation decoratedFileOperation;
public FileOperationDecorator(FileOperation decoratedFileOperation) {
this.decoratedFileOperation = decoratedFileOperation;
}
@Override
public void write(String filePath, String content) {
decoratedFileOperation.write(filePath, content);
}
@Override
public String read(String filePath) {
return decoratedFileOperation.read(filePath);
}
}
- 实现了
FileOperation
接口, 并包含一个FileOperation
类型的成员变量, 用于引用被装饰的对象, 默认将所有方法调用转发给被装饰的对象。
具体装饰器类
java
public class EncryptionDecorator extends FileOperationDecorator {
public EncryptionDecorator(FileOperation decoratedFileOperation) {
super(decoratedFileOperation);
}
private String encrypt(String data) {
System.out.println("[加密装饰器] 正在加密数据...");
return Base64.getEncoder().encodeToString(data.getBytes(StandardCharsets.UTF_8));
}
private String decrypt(String encryptedData) {
System.out.println("[加密装饰器] 正在解密数据...");
return new String(Base64.getDecoder().decode(encryptedData), StandardCharsets.UTF_8);
}
@Override
public void write(String filePath, String content) {
String encryptedContent = this.encrypt(content);
System.out.println("[加密装饰器] 加入加密后的内容。");
decoratedFileOperation.write(filePath, encryptedContent);
}
@Override
public String read(String filePath) {
String encryptedContent = decoratedFileOperation.read(filePath);
if (encryptedContent != null && !encryptedContent.isEmpty()) {
System.out.println("[加密装饰器] 读取到加密内容, 正在解密。");
return decrypt(encryptedContent);
}
return "";
}
}
public class CompressionDecorator extends FileOperationDecorator {
public CompressionDecorator(FileOperation decoratedFileOperation) {
super(decoratedFileOperation);
}
private byte[] compress(String data) throws IOException {
System.out.println("[压缩装饰器] 正在压缩数据...");
byte[] input = data.getBytes(StandardCharsets.UTF_8);
Deflater deflater = new Deflater();
deflater.setInput(input);
deflater.finish();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(input.length);
byte[] buffer = new byte[1024];
while (!deflater.finished()) {
int count = deflater.deflate(buffer);
outputStream.write(buffer, 0, count);
}
outputStream.close();
return outputStream.toByteArray();
}
private String decompress(byte[] compressedData) throws IOException, DataFormatException {
System.out.println("[压缩装饰器] 正在解压缩数据...");
Inflater inflater = new Inflater();
inflater.setInput(compressedData);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(compressedData.length);
byte[] buffer = new byte[1024];
while (!inflater.finished()) {
int count = inflater.inflate(buffer);
outputStream.write(buffer, 0, count);
}
outputStream.close();
return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
}
@Override
public void write(String filePath, String content) {
try {
byte[] compressedContent = this.compress(content);
String encodedCompressedContent = Base64.getEncoder().encodeToString(compressedContent);
System.out.println("[压缩装饰器] 写入压缩并编码后的内容。");
decoratedFileOperation.write(filePath, encodedCompressedContent);
} catch (IOException e) {
System.out.println("压缩写入失败: " + e.getMessage());
}
}
@Override
public String read(String filePath) {
String encodedCompressedContent = decoratedFileOperation.read(filePath);
if (encodedCompressedContent != null && !encodedCompressedContent.isEmpty()) {
try {
// 将 Base64 字符串解码为字节数组
byte[] compressedContent = Base64.getDecoder().decode(encodedCompressedContent);
System.out.println("[压缩装饰器] 读取到压缩内容, 正在解压缩。");
return this.decompress(compressedContent);
} catch (IOException | DataFormatException e) {
System.out.println("解压缩读取失败: " + e.getMessage());
}
}
return "";
}
}
EncryptionDecorator
具体的加密装饰器, 继承FileOperationDecorator
, 在write
方法中对内容进行加密后再写入, 在read
方法中读取加密内容后再解密。CompressionDecorator
具体的压缩装饰器, 继承FileOperationDecorator
, 在write
方法中对内容进行压缩后再写入, 在read
方法中读取压缩内容后再解压缩。
测试类
ini
public class ProxyDecoratorText {
@Test
public void test_proxyAndDecorator() {
System.out.println("\n--- 场景1: 普通用户写入普通文件 ---");
FileOperation normalUserFileOp = new FileAccessProxy("user1");
normalUserFileOp.write("normal_file.txt", "这是一段普通文本内容。");
String readContent1 = normalUserFileOp.read("normal_file.txt");
System.out.println("最终读取内容: " + readContent1);
System.out.println("\n--- 场景2: 普通用户尝试写入敏感文件 ---");
normalUserFileOp.write("sensitive_data.txt", "这是敏感数据。");
System.out.println("\n--- 场景3: 管理员写入加密的敏感文件 ---");
FileOperation adminFileOp = new FileAccessProxy("admin");
EncryptionDecorator encryptionAdminFileOp = new EncryptionDecorator(adminFileOp);
encryptionAdminFileOp.write("sensitive_encrypted_file.txt", "这是一段需要加密的敏感信息。");
String readContent2 = encryptionAdminFileOp.read("sensitive_encrypted_file.txt");
System.out.println("最终读取内容: " + readContent2);
System.out.println("\n--- 场景4: 管理员写入压缩并加密的大文件 ---");
String largeContent = "这是一段非常非常长的大文本内容,需要进行压缩和加密处理,以节省存储空间并保证数据安全。";
CompressionDecorator compressionEncryptedAdminFileOp = new CompressionDecorator(new EncryptionDecorator(adminFileOp));
compressionEncryptedAdminFileOp.write("large_compressed_encrypted_file.txt", largeContent);
String readContent3 = compressionEncryptedAdminFileOp.read("large_compressed_encrypted_file.txt");
System.out.println("最终读取内容: " + readContent3);
}
}
运行结果
ini
--- 场景1: 普通用户写入普通文件 ---
[文件访问代理] 权限检查通过: 用户 user1 可以执行 write 操作。
[真实文件操作] 正在将内容写入文件: normal_file.txt
写入成功, 文件内容为: 这是一段普通文本内容。
[文件访问代理] 权限检查通过: 用户 user1 可以执行 read 操作。
[真实文件操作] 正在从文件读取内容: normal_file.txt
读取成功, 文件内容为: 这是一段普通文本内容。
最终读取内容: 这是一段普通文本内容。
--- 场景2: 普通用户尝试写入敏感文件 ---
[文件访问代理] 权限不足: 用户 user1 无权写入敏感文件: sensitive_data.txt
--- 场景3: 管理员写入加密的敏感文件 ---
[加密装饰器] 正在加密数据...
[加密装饰器] 加入加密后的内容。
[文件访问代理] 权限检查通过: 用户 admin 可以执行 write 操作。
[真实文件操作] 正在将内容写入文件: sensitive_encrypted_file.txt
写入成功, 文件内容为: 6L+Z5piv5LiA5q616ZyA6KaB5Yqg5a+G55qE5pWP5oSf5L+h5oGv44CC
[文件访问代理] 权限检查通过: 用户 admin 可以执行 read 操作。
[真实文件操作] 正在从文件读取内容: sensitive_encrypted_file.txt
读取成功, 文件内容为: 6L+Z5piv5LiA5q616ZyA6KaB5Yqg5a+G55qE5pWP5oSf5L+h5oGv44CC
[加密装饰器] 读取到加密内容, 正在解密。
[加密装饰器] 正在解密数据...
最终读取内容: 这是一段需要加密的敏感信息。
--- 场景4: 管理员写入压缩并加密的大文件 ---
[压缩装饰器] 正在压缩数据...
[压缩装饰器] 写入压缩并编码后的内容。
[加密装饰器] 正在加密数据...
[加密装饰器] 加入加密后的内容。
[文件访问代理] 权限检查通过: 用户 admin 可以执行 write 操作。
[真实文件操作] 正在将内容写入文件: large_compressed_encrypted_file.txt
写入成功, 文件内容为: ZUp3dHlzOE93VEFBQitCWGI1ZXRZa3BFTW9aT1NJWXRtZFlCNlZqU2g5RmYvNXk4QWdlWDcvUUZzM2Fsc3BvNGVZL1ZEbHIvTFl6ZnBLalBiamx5b2dQTElQdlB3S01nNFVTRDJZWUR4MnppaHhZTGpud1B4VkNuZnM1K3g3Nk9JVSs4b0xpVVNCcmZQdVBxaHY1aFRSVVVkY1hWVFNYa0dGbnpKc2tYa2JCYit3PT0=
[文件访问代理] 权限检查通过: 用户 admin 可以执行 read 操作。
[真实文件操作] 正在从文件读取内容: large_compressed_encrypted_file.txt
读取成功, 文件内容为: ZUp3dHlzOE93VEFBQitCWGI1ZXRZa3BFTW9aT1NJWXRtZFlCNlZqU2g5RmYvNXk4QWdlWDcvUUZzM2Fsc3BvNGVZL1ZEbHIvTFl6ZnBLalBiamx5b2dQTElQdlB3S01nNFVTRDJZWUR4MnppaHhZTGpud1B4VkNuZnM1K3g3Nk9JVSs4b0xpVVNCcmZQdVBxaHY1aFRSVVVkY1hWVFNYa0dGbnpKc2tYa2JCYit3PT0=
[加密装饰器] 读取到加密内容, 正在解密。
[加密装饰器] 正在解密数据...
[压缩装饰器] 读取到压缩内容, 正在解压缩。
[压缩装饰器] 正在解压缩数据...
最终读取内容: 这是一段非常非常长的大文本内容,需要进行压缩和加密处理,以节省存储空间并保证数据安全。
Process finished with exit code 0
组合优势
- 代理模式负责基础的访问控制, 而装饰器模式负责功能增强, 使得每个类的职责更加单一。
- 装饰器模式允许在运行时动态地组合多种功能, 代理模式作为功能链的起点, 确保所有操作都经过必要的访问控制, 为后续的功能增强提供了安全的基础。
代理模式+策略模式
策略模式
行为型设计模式, 定义一系列算法, 将每个算法封装并使它们可以互相替换。
案例
在电商支付系统中, 用户可以选择多种支付方式(如信用卡支付、支付宝支付、微信支付), 每种支付方式的实现细节不同, 但它们都提供了一个统一的 支付接口, 同时, 为确保系统的安全性和合规性, 所有支付操作都需要进行严格的安全审计。
模式职责
- 代理模式: 作为支付操作的统一入口, 负责在实际支付操作执行前后进行安全审计、日志记录、身份验证等非核心业务逻辑, 确保了所有支付请求都经过必要的安全检查。
- 策略模式: 封装不同的支付算法, 使得这些支付方式可以相互替换, 上下文根据用户的选择, 动态地使用不同的支付策略来完成支付。
代码示例
抽象策略
csharp
public interface PaymentStrategy {
void pay(double amount);
}
- 定义了具体支付方式必须实现的
pay(double amount)
方法。
具体策略
typescript
public class CreditCardPaymentStrategy implements PaymentStrategy {
private String cardNumber;
private String expiryDate;
public CreditCardPaymentStrategy(String cardNumber, String expiryDate) {
this.cardNumber = cardNumber;
this.expiryDate = expiryDate;
}
@Override
public void pay(double amount) {
System.out.println("[信用卡支付策略] 正在使用信用卡 " + cardNumber + " (有效期: " + expiryDate + ") 支付" + amount + " 元。");
System.out.println("[信用卡支付策略] 支付成功。");
}
}
public class AlipayPaymentStrategy implements PaymentStrategy {
private String userId;
public AlipayPaymentStrategy(String userId) {
this.userId = userId;
}
@Override
public void pay(double amount) {
System.out.println("[支付宝支付策略] 正在使用支付宝账户 " + userId + " 支付 " + amount + " 元。");
System.out.println("[支付宝支付策略] 支付成功。");
}
}
public class WechatPaymentStrategy implements PaymentStrategy {
private String openId;
public WechatPaymentStrategy(String openId) {
this.openId = openId;
}
@Override
public void pay(double amount) {
System.out.println("[微信支付策略] 正在使用微信账户 " + openId + " 支付 " + amount + " 元。");
System.out.println("[微信支付策略] 支付成功。");
}
}
CreditCardPaymentStrategy
、AlipayPaymentStrategy
、WechatPaymentStrategy
分别实现了PaymentStrategy
接口, 并封装了各自支付方式的详细逻辑, 具体策略类之间可以相互替换。
抽象主题/策略上下文接口
csharp
public interface PaymentService {
void processPayment(double amount, PaymentStrategy strategy);
}
- 定义了
processPayment(double amount, PaymentStrategy strategy)
方法, 接受支付金额和PaymentStrategy
作为参数。
真实主题
csharp
public class RealPaymentService implements PaymentService {
@Override
public void processPayment(double amount, PaymentStrategy strategy) {
System.out.println("\n[真实支付服务] 开始处理支付请求, 金额: " + amount + " 元。");
if (strategy == null) {
System.out.println("[真实支付服务] 错误: 未指定支付策略。");
return;
}
strategy.pay(amount);
System.out.println("[真实支付服务] 支付请求处理完毕。");
}
}
- 持有一个
PaymentStrategy
的引用, 并在processPayment
方法中调用该策略的pay
方法来完成实际支付。
代理主题
csharp
public class SecurityAuditPaymentProxy implements PaymentService {
private RealPaymentService realPaymentService;
private String currentUserId;
public SecurityAuditPaymentProxy(RealPaymentService realPaymentService, String currentUserId) {
this.realPaymentService = realPaymentService;
this.currentUserId = currentUserId;
}
private boolean preAudit(double amount, PaymentStrategy strategy) {
System.out.println("\n[安全审计代理] 前置审计: 用户 " + currentUserId + " 尝试支付 " + amount + " 元, 使用策略: " + strategy.getClass().getSimpleName() + " 在 " + new Date());
if ("blockedUser".equals(currentUserId)) {
System.out.println("[安全审计代理] 审计失败: 用户 " + currentUserId + "被阻止支付。");
return false;
}
System.out.println("[安全审计代理] 前置审计通过。");
return true;
}
private void postAudit(double amount, PaymentStrategy strategy) {
System.out.println("[安全审计代理] 后置审计: 用户 " + currentUserId + " 支付 " + amount + " 元操作完成, 使用策略: " + strategy.getClass().getSimpleName() + "..");
}
@Override
public void processPayment(double amount, PaymentStrategy strategy) {
if (this.preAudit(amount, strategy)) {
realPaymentService.processPayment(amount, strategy);
this.postAudit(amount, strategy);
}
else {
System.out.println("[安全审计代理] 支付请求被拒绝。");
}
}
}
- 实现了
PaymentService
接口, 并持有一个RealPaymentService
的引用。在processPayment
方法中, 在调用realPaymentService.processPayment
之前 执行preAudit
(前置审计), 在之后执行postAudit
。
测试类
ini
public class ProxyStrategyTest {
@Test
public void test_proxyStrategy() {
System.out.println("\n--- 用户1(正常用户)使用信用卡支付 ---");
PaymentService user1PaymentService = new SecurityAuditPaymentProxy(new RealPaymentService(), "user_A");
PaymentStrategy creditCardStrategy = new CreditCardPaymentStrategy("1234-5678-9012", "12/25");
user1PaymentService.processPayment(100.00, creditCardStrategy);
System.out.println("\n--- 用户2(正常用户)使用支付宝支付 ---");
PaymentService user2PaymentService = new SecurityAuditPaymentProxy(new RealPaymentService(), "user_B");
PaymentStrategy alipayStrategy = new AlipayPaymentStrategy("alipay_user_B");
user2PaymentService.processPayment(50.50, alipayStrategy);
System.out.println("\n--- 用户3(被阻止用户)尝试使用微信支付 ---");
PaymentService user3PaymentService = new SecurityAuditPaymentProxy(new RealPaymentService(), "blockedUser");
PaymentStrategy wechatStrategy = new WechatPaymentStrategy("wechat_open_id_C");
user3PaymentService.processPayment(200.00, wechatStrategy);
System.out.println("\n--- 用户4(正常用户)再次使用支付宝支付 ---");
PaymentService user4PaymentService = new SecurityAuditPaymentProxy(new RealPaymentService(), "user_D");
PaymentStrategy anotherAlipayStrategy = new AlipayPaymentStrategy("alipay_user_D");
user4PaymentService.processPayment(75.00, anotherAlipayStrategy);
}
}
执行结果
less
--- 用户1(正常用户)使用信用卡支付 ---
[安全审计代理] 前置审计: 用户 user_A 尝试支付 100.0 元, 使用策略: CreditCardPaymentStrategy 在 Mon Sep 29 10:28:28 CST 2025
[安全审计代理] 前置审计通过。
[真实支付服务] 开始处理支付请求, 金额: 100.0 元。
[信用卡支付策略] 正在使用信用卡 1234-5678-9012 (有效期: 12/25) 支付100.0 元。
[信用卡支付策略] 支付成功。
[真实支付服务] 支付请求处理完毕。
[安全审计代理] 后置审计: 用户 user_A 支付 100.0 元操作完成, 使用策略: CreditCardPaymentStrategy..
--- 用户2(正常用户)使用支付宝支付 ---
[安全审计代理] 前置审计: 用户 user_B 尝试支付 50.5 元, 使用策略: AlipayPaymentStrategy 在 Mon Sep 29 10:28:28 CST 2025
[安全审计代理] 前置审计通过。
[真实支付服务] 开始处理支付请求, 金额: 50.5 元。
[支付宝支付策略] 正在使用支付宝账户 alipay_user_B 支付 50.5 元。
[支付宝支付策略] 支付成功。
[真实支付服务] 支付请求处理完毕。
[安全审计代理] 后置审计: 用户 user_B 支付 50.5 元操作完成, 使用策略: AlipayPaymentStrategy..
--- 用户3(被阻止用户)尝试使用微信支付 ---
[安全审计代理] 前置审计: 用户 blockedUser 尝试支付 200.0 元, 使用策略: WechatPaymentStrategy 在 Mon Sep 29 10:28:28 CST 2025
[安全审计代理] 审计失败: 用户 blockedUser被阻止支付。
[安全审计代理] 支付请求被拒绝。
--- 用户4(正常用户)再次使用支付宝支付 ---
[安全审计代理] 前置审计: 用户 user_D 尝试支付 75.0 元, 使用策略: AlipayPaymentStrategy 在 Mon Sep 29 10:28:28 CST 2025
[安全审计代理] 前置审计通过。
[真实支付服务] 开始处理支付请求, 金额: 75.0 元。
[支付宝支付策略] 正在使用支付宝账户 alipay_user_D 支付 75.0 元。
[支付宝支付策略] 支付成功。
[真实支付服务] 支付请求处理完毕。
[安全审计代理] 后置审计: 用户 user_D 支付 75.0 元操作完成, 使用策略: AlipayPaymentStrategy..
Process finished with exit code 0
组合优势
- 当需要增加新的支付方式时, 只需添加新的具体策略类; 当安全审计需求变化时, 只需修改代理类。
- 代理模式专注于安全审计和访问控制, 而策略模式专注于封装不同的支付算法, 两者职责明确, 互不干扰。