一、定义与核心思想
代理模式是一种结构型设计模式,其核心思想是提供一个代理对象,用以控制对实际对象的访问。通过代理对象,可以在不改变实际对象的情况下,添加各种功能,如权限控制、懒加载、缓存、远程调用等。
二、组成要素
代理模式主要由以下几个要素组成:
-
抽象主题(Subject)
-
这是一个接口或抽象类,定义了代理对象和实际对象共同的接口,使得代理对象可以在客户端中替代实际对象使用。
-
例如,定义一个
Subject
接口,其中声明了request()
方法。
-
-
实际主题(RealSubject)
-
实现抽象主题接口的具体类,定义了代理所代表的真实对象。实际主题对象实现了业务逻辑,是客户端最终需要调用的对象。
-
例如,
RealSubject
类实现了Subject
接口,在request()
方法中实现了具体的业务逻辑。
-
-
代理(Proxy)
-
也实现了抽象主题接口,内部包含一个对实际主题对象的引用。代理对象在调用实际主题对象的方法前后可以添加额外的逻辑,从而控制对实际主题对象的访问。
-
例如,
Proxy
类实现了Subject
接口,并且有一个RealSubject
类型的成员变量。在request()
方法中,代理对象可以进行权限检查、日志记录等操作,然后调用实际主题对象的request()
方法。
-
三、实现示例
以下是使用Java语言实现代理模式的一个简单示例:
// 抽象主题接口
interface Subject {
void request();
}
// 实际主题类
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("实际对象的请求");
}
}
// 代理类
class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
preRequest();
if (realSubject == null) {
realSubject = new RealSubject();
}
realSubject.request();
postRequest();
}
private void preRequest() {
System.out.println("代理对象的预处理操作,如权限检查");
}
private void postRequest() {
System.out.println("代理对象的后处理操作,如日志记录");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy(); // 直接创建代理对象
proxy.request(); // 通过代理对象调用方法
}
}
在这个示例中,客户端通过代理对象proxy
调用request()
方法。代理对象在调用实际主题对象RealSubject
的request()
方法前后,分别进行了预处理和后处理操作,如权限检查和日志记录。
四、优点
-
职责分离
-
代理对象可以将客户端与实际主题对象解耦,使得客户端不需要直接与实际主题对象交互,从而降低了系统的耦合度。
-
-
功能扩展
-
代理对象可以在不修改实际主题对象的情况下,添加各种功能,如权限控制、缓存、懒加载等,符合开闭原则。
-
-
控制访问
-
代理对象可以控制对实际主题对象的访问,例如,根据用户的权限决定是否允许调用实际主题对象的方法。
-
-
远程调用
-
代理对象可以处理远程方法调用的细节,如网络通信、序列化/反序列化等,使得客户端可以透明地调用远程对象的方法。
-
-
延迟加载
-
代理对象可以实现懒加载,只有在第一次调用方法时才创建实际主题对象,从而提高系统的性能。
-
五、缺点
-
增加复杂性
-
代理模式会增加系统的复杂性,因为需要创建额外的代理类和管理代理对象与实际对象之间的关系。
-
-
性能开销
-
代理对象的调用会增加额外的性能开销,特别是当代理对象的逻辑比较复杂时,可能会对系统的性能产生一定的影响。
-
六、应用场景
-
权限控制
-
通过代理对象可以控制对实际对象的访问权限,例如,根据用户的权限决定是否允许调用某个方法或访问某个资源。
-
例如,实现一个权限控制代理,只有具备相应权限的用户才能通过代理对象调用实际对象的方法。
-
-
远程调用
-
代理对象可以处理远程方法调用的细节,使得客户端可以透明地调用远程服务器上的方法。
-
例如,使用RMI(Remote Method Invocation)技术实现远程对象调用,客户端通过代理对象调用远程服务器上的方法。
-
-
懒加载
-
代理对象可以实现懒加载,只有在第一次调用方法时才创建实际对象,从而提高系统的性能。
-
例如,加载一个大型的数据库连接对象时,可以使用懒加载代理来延迟创建连接对象,直到真正需要使用时才创建。
-
-
缓存
-
代理对象可以缓存方法的调用结果,当再次调用相同的方法时,直接返回缓存的结果,从而提高系统的性能。
-
例如,缓存网页的静态内容,当用户再次请求相同的内容时,直接从缓存中获取,而不需要重新生成。
-
-
日志记录
-
代理对象可以在调用实际对象的方法前后记录日志,用于监控和调试。
-
例如,记录方法的调用时间、参数、返回值等信息,帮助开发者进行问题定位和性能优化。
-
七、动态代理
除了静态代理,Java还支持动态代理。动态代理可以在运行时动态创建代理对象,而不需要在编译时定义代理类。动态代理主要使用java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现。
动态代理示例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 抽象主题接口
interface Subject {
void request();
}
// 实际主题类
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("实际对象的请求");
}
}
// 调用处理器
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
preRequest();
Object result = method.invoke(target, args);
postRequest();
return result;
}
private void preRequest() {
System.out.println("代理对象的预处理操作,如权限检查");
}
private void postRequest() {
System.out.println("代理对象的后处理操作,如日志记录");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Subject realSubject = new RealSubject(); // 创建实际主题对象
MyInvocationHandler handler = new MyInvocationHandler(realSubject); // 创建调用处理器并传入实际主题对象
Subject proxy = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(), // 目标类加载器
new Class<?>[]{Subject.class}, // 目标类实现的接口
handler // 调用处理器
);
proxy.request(); // 通过代理对象调用方法
}
}
在这个示例中,客户端通过Proxy.newProxyInstance
方法动态创建了一个代理对象proxy
。代理对象的调用处理器MyInvocationHandler
在调用实际主题对象RealSubject
的方法前后,分别进行了预处理和后处理操作。
总结
代理模式通过代理对象控制对实际对象的访问,可以在不修改实际对象的情况下,添加各种功能,如权限控制、懒加载、缓存、远程调用等。代理模式在实际开发中非常有用,选择静态代理还是动态代理取决于具体的需求和场景。