设计模式--代理模式:控制对象访问的优雅设计
·
代理模式入门:控制对象访问的优雅设计
今天我们来深入探讨代理模式(Proxy Pattern),一种结构型设计模式,用于通过代理对象控制对真实对象的访问。代理模式可以在不修改原有类的情况下添加额外的逻辑,如权限控制、延迟加载或日志记录。本文将带你实现一个简单的代理模式示例,适合初学者快速上手,同时为有经验的开发者提供进阶建议和优化思路。
代理模式在现实生活中类似中介,代理对象在客户端和真实对象之间扮演桥梁角色。本文使用 Java 语言,通过一个文件访问控制的场景展示代理模式的实现。让我们开始吧!
前置准备
在开始之前,确保开发环境已就绪:
- JDK:推荐 JDK 17(也可使用 JDK 8+)。
- IDE:IntelliJ IDEA、Eclipse 或 VS Code,推荐支持 Java 的 IDE。
- 构建工具:Maven(可选,用于管理依赖)。
- 项目结构:创建一个简单的 Java 项目,目录如下:
proxy-pattern-demo ├── src │ ├── main │ │ ├── java │ │ │ └── com.example.proxy │ │ │ ├── subject │ │ │ ├── proxy │ │ │ └── Main.java │ └── test └── pom.xml
安装环境:
- 确保 JDK 已安装:
java -version. - Maven(可选):
mvn -version. - 无需额外依赖,本示例使用纯 Java。
步骤 1: 定义抽象主题
代理模式需要一个抽象主题接口,定义真实对象和代理的公共行为。在 com.example.proxy.subject.FileAccess 中:
package com.example.proxy.subject;
public interface FileAccess {
void readFile(String fileName);
}
说明:
FileAccess是目标接口,定义文件读取操作。
步骤 2: 创建真实主题
实现具体的文件访问类。在 com.example.proxy.subject.RealFileAccess 中:
package com.example.proxy.subject;
public class RealFileAccess implements FileAccess {
@Override
public void readFile(String fileName) {
System.out.println("Reading file: " + fileName);
}
}
说明:
RealFileAccess是真实对象,执行实际的文件读取操作。
步骤 3: 创建代理类
创建代理类,控制对真实对象的访问并添加额外逻辑。在 com.example.proxy.proxy.FileAccessProxy 中:
package com.example.proxy.proxy;
import com.example.proxy.subject.FileAccess;
import com.example.proxy.subject.RealFileAccess;
public class FileAccessProxy implements FileAccess {
private RealFileAccess realFileAccess;
private final String userRole;
public FileAccessProxy(String userRole) {
this.userRole = userRole;
}
@Override
public void readFile(String fileName) {
if (!"admin".equalsIgnoreCase(userRole)) {
System.out.println("Access denied for user with role: " + userRole);
return;
}
if (realFileAccess == null) {
realFileAccess = new RealFileAccess();
}
realFileAccess.readFile(fileName);
}
}
说明:
- 代理类实现
FileAccess接口,包装RealFileAccess。 - 添加权限控制,仅允许
admin角色访问。 - 延迟加载:
realFileAccess在首次调用时初始化。
步骤 4: 客户端代码
在 com.example.proxy.Main 中测试代理模式:
package com.example.proxy;
import com.example.proxy.proxy.FileAccessProxy;
import com.example.proxy.subject.FileAccess;
public class Main {
public static void main(String[] args) {
// 非管理员用户
FileAccess userAccess = new FileAccessProxy("user");
userAccess.readFile("secret.txt");
// 管理员用户
FileAccess adminAccess = new FileAccessProxy("admin");
adminAccess.readFile("secret.txt");
}
}
运行输出:
Access denied for user with role: user
Reading file: secret.txt
步骤 5: 运行和测试
-
编译和运行:
- 在 IDE 中运行
Main类。 - 或使用命令行:
javac src/main/java/com/example/proxy/*.java src/main/java/com/example/proxy/*/*.java java com.example.proxy.Main
- 在 IDE 中运行
-
测试用例:
- 验证非管理员用户被拒绝访问。
- 验证管理员用户成功读取文件。
- 测试延迟加载:确认
RealFileAccess未在代理初始化时创建。
-
调试技巧:
- 添加日志:使用
System.out或 SLF4J 记录权限检查和调用过程。 - 检查对象状态:在调试器中验证
realFileAccess的延迟初始化。 - 异常处理:在代理中添加输入验证(如文件名非空)。
- 添加日志:使用
进阶与最佳实践
-
代理类型:
- 虚拟代理:如本示例的延迟加载。
- 保护代理:本示例的权限控制。
- 远程代理:用于远程服务调用(如 RMI)。
- 动态代理:使用 Java 的
java.lang.reflect.Proxy:import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public FileAccess createDynamicProxy(String userRole) { return (FileAccess) Proxy.newProxyInstance( FileAccess.class.getClassLoader(), new Class<?>[]{FileAccess.class}, (proxy, method, args) -> { if (!"admin".equalsIgnoreCase(userRole)) { System.out.println("Access denied for user with role: " + userRole); return null; } RealFileAccess real = new RealFileAccess(); return method.invoke(real, args); } ); }
-
异常处理:
- 添加自定义异常:
public void readFile(String fileName) { if (fileName == null || fileName.isEmpty()) { throw new IllegalArgumentException("File name cannot be empty"); } // ... }
- 添加自定义异常:
-
性能优化:
- 缓存权限检查结果:
private boolean accessChecked = false; private boolean accessGranted = false; public void readFile(String fileName) { if (!accessChecked) { accessGranted = "admin".equalsIgnoreCase(userRole); accessChecked = true; } if (!accessGranted) { System.out.println("Access denied for user with role: " + userRole); return; } // ... }
- 缓存权限检查结果:
-
测试:
- 使用 JUnit 编写单元测试:
import org.junit.Test; import static org.junit.Assert.*; public class FileAccessProxyTest { @Test public void testAdminAccess() { FileAccess proxy = new FileAccessProxy("admin"); proxy.readFile("test.txt"); // 验证输出 } @Test public void testUserAccess() { FileAccess proxy = new FileAccessProxy("user"); proxy.readFile("test.txt"); // 验证拒绝 } }
- 使用 JUnit 编写单元测试:
-
其他应用场景:
- 日志记录:在代理中添加日志功能。
- 缓存代理:缓存真实对象的结果。
- AOP(面向切面编程):Spring AOP 使用动态代理实现。
-
资源推荐:书籍《设计模式:可复用面向对象软件的基础》、Refactoring Guru 网站。多实践其他设计模式(如装饰者模式、观察者模式)。
总结
通过这个代理模式示例,你学会了如何通过代理控制对象访问,实现了权限管理和延迟加载。代理模式在需要控制访问、延迟初始化或添加横切关注点时非常实用,广泛应用于框架和分布式系统。
更多推荐



所有评论(0)