设计模式 – 代理模式
前置知识:
"主题"是接口或抽象类的统称,定义了行为规范;
"主体"是实现这些行为的具体对象,是实际执行业务逻辑的实体。
一、概念
代理模式是一种结构型设计模式。
它通过提供一个代理类来代表或“代理”另一个类(称为真实主题)。
代理类和真实主题具有相同的接口,因此可以在不改变原有代码的基础上,对真实主题的功能进行增强、控制访问或提供额外的逻辑处理。
换句话说,就是用于为其他对象提供一个替身或占位符,以控制对这个对象的访问。
举例说明:
A是买房人(真实主题)、B是全权委托人:中介(代理)、C是卖房人(调用方)。
卖房人在让买房人上门看房时,中介可以先看然后再让买房人来看房子;卖房人与买房人签订购买合同时,也要中介先看一次再把合同交给买房人,签完后再给到中介,由中介转交给卖房人。
看房是买房人在看,真正签合同出钱的还是买房人。那么加这个中介的目的是什么呢?
中介可以在买房人看房前先看并查询房屋的使用问题、质量问题、邻里关系、卖房人身份信息等,在确认没有问题后再让买房人上门看房;
在签订购买协议前先看可以核实合同的有效性、合同内容等,签完合同后看买卖双方是否签错、漏签等。
甚至买卖双方完全可以没见过面。看房由中介和买房人视频看,签合同也可以由中介来回转交,从而让买卖双方在未见面的情况下完成交易,从而实现解耦。
中介在不破坏卖房人和买房人原本的行为的同时,扦插进入很多新的行为,这些行为会在某个时间点被触发从而被执行,以此来加入更多的功能和作用。
二、分类
代理模式主要分为以下几类:
静态代理: 代理类在编译期间就被创建,程序员手动实现具体代理类,直接引用真实主题,并在其中添加额外逻辑。
动态代理:
- JDK动态代理: 基于Java的反射机制,为接口生成代理类,适用于目标对象实现了接口的情况。
- CGLIB动态代理: 通过字节码操作,为没有实现接口的目标对象生成子类代理,适合任何类。
三、使用动机和目的
动机: 在不修改原有代码的情况下,增强或控制对目标对象的访问,比如增加日志、安全控制、延迟初始化等。
目的: 提高系统的灵活性、扩展性和可维护性,同时降低模块间的耦合度。
四、核心组件与关系
- Subject(主题接口): 定义了代理类和真实主题的公共接口。
- RealSubject(真实主题): 实现主题接口,包含核心业务逻辑。
- Proxy(代理): 同样实现主题接口,内部持有一个真实主题的引用,可以预处理请求、后处理结果,或控制对真实主题的访问。
五、优缺点
5.1 优点
- 增加灵活性: 可以在不修改目标对象的情况下,添加额外功能。
- 控制访问: 可以控制对真实主题的直接访问,增强安全性。
- 延迟初始化: 通过代理可以实现懒加载,提高性能。
- 扩展功能: 易于扩展系统功能,符合开闭原则。
5.2 缺点
- 增加系统复杂度: 引入代理类,增加了类的数量和系统的复杂度。
- 性能开销: 特别是动态代理,可能会带来一定的性能损失。
六、应用场景
- 远程代理: 为远程服务提供本地代理对象,隐藏网络通信细节。
- 虚拟代理: 对资源密集型对象进行延迟加载,如大图像、大数据集。
- 保护代理: 控制对敏感对象的访问,实施权限检查。
- 日志代理: 在方法调用前后添加日志记录。
- 选用时机: 当需要在不修改原对象的基础上,增强其功能或控制访问时,考虑使用代理模式。
七、注意事项
- 平衡性能与灵活性: 根据实际情况权衡静态代理和动态代理的使用。
- 设计清晰的接口: 确保代理类和真实主题遵循相同的接口规范,保证替换的透明性。
- 适度使用: 避免过度设计,只在确实需要增强或控制的地方使用代理。
八、代码实现
8.1 静态代理示例
这段代码演示了Java中代理模式的应用,具体是静态代理的实现方式。
代理模式主要用于在不修改原有对象的基础上,提供额外的处理逻辑,比如控制访问、增强功能等。
下面是对代码中代理模式使用的详细解释:
主题接口 (Image)
首先定义了一个接口 Image
,它声明了一个方法 display()
。
这个接口充当了代理模式中的“主题接口”,定义了真实主题和代理需要实现的行为规范。
// 主题接口
interface Image {
void display(); // 显示
}
真实主题 (RealImage)
RealImage
类实现了 Image
接口,代表了真正的业务逻辑对象,即“真实主题”。
在这个例子中,RealImage
负责加载并显示图片,模拟了可能较为耗时或资源密集的操作(比如从磁盘加载图片)。
// 真实主题
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(filename);
}
// 从磁盘加载
private void loadFromDisk(String filename) {
// 加载图片的逻辑
System.out.println("Loading " + filename);
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
静态代理 (ImageProxy)
ImageProxy
同样实现了 Image
接口。
作为真实主题 RealImage
的代理,它的主要作用是控制对 RealImage
实例的访问,实现延迟加载逻辑,即只有在真正需要显示图片时才创建 RealImage
对象,从而提高性能,减少不必要的资源消耗。
// 静态代理
class ImageProxy implements Image {
private RealImage realImage; // 真实主题实例
private String filename;
public ImageProxy(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
客户端代码
客户端代码通过 ImageProxy
来请求显示图片,而不需要直接与 RealImage
交互。
这样,即使多次调用 display()
方法,图片也只会被加载一次,因为代理负责管理 RealImage
的创建和复用。
// 客户端代码
public class Client {
public static void main(String[] args) {
Image image = new ImageProxy("hi-res-image.jpg");
image.display();
}
}
总结来说,这段代码展示了代理模式如何通过引入代理类(ImageProxy
)来控制对真实主题(RealImage
)的访问,实现了延迟加载图片的功能,提高了程序的效率和响应速度。
代理模式的关键在于代理类与真实主题遵循相同的接口,从而对客户端透明,使其能够以一致的方式对待真实主题和代理。
8.2 动态代理示例(JDK动态代理)
InvocationHandler
:调用处理程序接口
InvocationHandler
接口是JDK提供的,它是一个核心接口,用于定义代理对象在调用其方法时所执行的处理逻辑。具体来说,实现这个接口的类需要重写
invoke
方法,这个方法会在代理对象的任何方法被调用时触发。在invoke
方法内部,你可以定义在调用真实对象之前或之后需要执行的任何操作,比如日志记录、权限验证、性能监控等附加功能。这为代理模式提供了极大的灵活性和扩展性。
此例中,我们将创建一个简单的日志记录功能,通过动态代理来增强一个简单消息服务的功能。
消息服务接口:MessageService
// 消息服务接口
public interface MessageService {
void sendMessage(String msg);
}
消息服务实现类:RealMessageService
// 真实主题:消息服务实现类
class RealMessageService implements MessageService {
@Override
public void sendMessage(String msg) {
System.out.println("发送消息内容: " + msg);
}
}
动态日志代理类:LoggingProxy
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 动态代理类
class LoggingProxy implements InvocationHandler {
private Object target;
public LoggingProxy(Object target) {
this.target = target;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("这是目标方法执行前日志,方法名: " + method.getName());
Object result = method.invoke(target, args); // 调用目标方法
System.out.println("这是目标方法执行后日志,方法名:" + method.getName());
return result;
}
}
客户端类:DynamicProxyDemo
// 客户端代码
public class DynamicProxyDemo {
public static void main(String[] args) {
// 创建真实主题实例
MessageService realService = new RealMessageService();
// 创建动态代理实例
LoggingProxy loggingProxy = new LoggingProxy(realService);
MessageService proxyService = (MessageService) loggingProxy.getProxyInstance();
// 通过代理调用方法
proxyService.sendMessage("Hello, world!");
}
}
运行结果:
这是目标方法执行前日志,方法名: sendMessage
发送消息内容: Hello, world!
这是目标方法执行后日志,方法名:sendMessage
这段代码演示了如何使用JDK动态代理来为 MessageService
接口的实现类 RealMessageService
添加日志记录功能。
LoggingProxy
类实现了 InvocationHandler
接口,它在调用真实主题的方法前后分别添加了日志记录逻辑。
在 main
方法中,我们创建了真实主题的实例,并通过 LoggingProxy
获取到代理对象,然后通过代理对象调用 sendMessage
方法,可以看到日志记录功能被正确执行。