定义:
代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义如下: Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供 一种代理以控制对这个对象的访问。)
代理模式通用类图
代理设计模式的核心思想是间接访问和控制增强。通过引入代理对象,将客户端对目标对象的直接访问转变为间接访问,代理对象充当客户端和目标对象之间的中介。在代理对象中,可以灵活地添加各种控制逻辑和增强功能,而无需修改目标对象的代码,从而实现对目标对象访问的控制和功能扩展,同时也符合开闭原则,提高了系统的可维护性和可扩展性。
角色:
代理模式包含以下几个核心角色:
1、抽象主题(Subject)
抽象主题定义了目标对象和代理对象的共同接口,它声明了客户端可以调用的方法。客户端通过抽象主题接口与代理对象或目标对象进行交互,确保客户端可以以一致的方式访问目标对象和代理对象。
2、目标对象(Real Subject)
目标对象是实际执行操作的对象,它实现了抽象主题接口中定义的方法,完成具体的业务逻辑。目标对象是客户端真正想要访问的对象,但客户端不直接访问它,而是通过代理对象间接访问。
3、代理对象(Proxy)
代理对象实现了抽象主题接口,并且持有一个目标对象的引用。代理对象在客户端调用其方法时,可以在调用目标对象方法之前或之后执行额外的操作,如进行权限检查、记录访问日志、缓存结果等。代理对象通过这种方式控制对目标对象的访问,并为目标对象增加额外的功能。
代码示例:
代理设计模式有多种实现方式,常见的有静态代理和动态代理。下面分别通过代码示例来介绍这两种实现方式。
(一)静态代理
静态代理是在编译期就确定代理类的代码,代理类和目标类实现相同的接口,代理类中包含对目标类的引用。
以网络请求为例,假设我们有一个NetworkRequest接口用于发起网络请求,RealNetworkRequest类是实际执行网络请求的目标对象,ProxyNetworkRequest是代理对象。
// 抽象主题:网络请求接口
public interface NetworkRequest {
void sendRequest(String url);
}
// 目标对象:实际的网络请求类
public class RealNetworkRequest implements NetworkRequest {
@Override
public void sendRequest(String url) {
System.out.println("正在向 " + url + " 发送网络请求");
}
}
// 代理对象:网络请求代理类
public class ProxyNetworkRequest implements NetworkRequest {
private RealNetworkRequest realNetworkRequest;
public ProxyNetworkRequest(RealNetworkRequest realNetworkRequest) {
this.realNetworkRequest = realNetworkRequest;
}
@Override
public void sendRequest(String url) {
// 调用目标对象方法前的操作:权限验证
System.out.println("进行权限验证");
realNetworkRequest.sendRequest(url);
// 调用目标对象方法后的操作:记录日志
System.out.println("记录网络请求日志");
}
}
// 客户端代码
public class StaticProxyClient {
public static void main(String[] args) {
RealNetworkRequest realRequest = new RealNetworkRequest();
ProxyNetworkRequest proxyRequest = new ProxyNetworkRequest(realRequest);
proxyRequest.sendRequest("https://example.com");
}
}
(二)动态代理
动态代理是在运行时动态生成代理类的字节码,并加载到内存中。Java 中提供了java.lang.reflect.Proxy类和InvocationHandler接口来实现动态代理。
同样以网络请求为例,代码如下:
// 抽象主题:网络请求接口
public interface NetworkRequest {
void sendRequest(String url);
}
// 目标对象:实际的网络请求类
public class RealNetworkRequest implements NetworkRequest {
@Override
public void sendRequest(String url) {
System.out.println("正在向 " + url + " 发送网络请求");
}
}
// 调用处理器:实现InvocationHandler接口
public class NetworkRequestInvocationHandler implements InvocationHandler {
private Object target;
public NetworkRequestInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用目标对象方法前的操作:权限验证
System.out.println("进行权限验证");
Object result = method.invoke(target, args);
// 调用目标对象方法后的操作:记录日志
System.out.println("记录网络请求日志");
return result;
}
}
// 客户端代码
public class DynamicProxyClient {
public static void main(String[] args) {
RealNetworkRequest realRequest = new RealNetworkRequest();
NetworkRequestInvocationHandler handler = new NetworkRequestInvocationHandler(realRequest);
NetworkRequest proxy = (NetworkRequest) Proxy.newProxyInstance(
realRequest.getClass().getClassLoader(),
realRequest.getClass().getInterfaces(),
handler
);
proxy.sendRequest("https://example.com");
}
}
优点 :
1、功能扩展灵活:通过代理对象可以在不修改目标对象代码的情况下,方便地为目标对象增加各种功能,如权限验证、日志记录、缓存处理等,符合开闭原则。
2、解耦客户端与目标对象:客户端只与代理对象交互,不直接依赖目标对象,降低了客户端与目标对象之间的耦合度,提高了系统的可维护性和可扩展性。
3、控制访问:代理对象可以对目标对象的访问进行控制,确保只有符合条件的访问才能到达目标对象,增强了系统的安全性和稳定性。
缺点:
1、增加系统复杂度:代理设计模式引入了代理对象,增加了系统的类数量和代码复杂度,尤其是在动态代理中,涉及到反射机制,使代码的理解和调试变得更加困难。
2、性能开销:代理对象在调用目标对象方法时,需要进行额外的方法调用和逻辑处理,会带来一定的性能开销。在一些对性能要求极高的场景中,需要谨慎使用代理设计模式。
使用场景:
(一)远程代理
当目标对象位于远程服务器上时,通过代理对象可以隐藏网络通信的细节,客户端只需与本地的代理对象交互,就像访问本地对象一样。例如,RPC(远程过程调用)框架中就广泛使用了远程代理。
(二)虚拟代理
对于创建开销较大的对象,如加载大尺寸图片、读取大型文件等,可以使用虚拟代理在需要时才创建目标对象,减少系统资源的浪费。在图片加载场景中,先显示一个占位图(代理对象),当真正需要显示图片时再加载实际的图片(目标对象)。
(三)保护代理
保护代理用于控制对目标对象的访问权限,在代理对象中进行权限验证、身份认证等操作。例如,在企业管理系统中,不同角色的用户对某些功能的访问权限不同,可以通过保护代理来实现权限控制。
(四)缓存代理
代理对象可以缓存目标对象的操作结果,当客户端再次请求相同操作时,直接返回缓存结果,提高系统的响应速度。在 Web 开发中,对于一些不经常变化的数据查询结果,可以使用缓存代理进行缓存。
代理设计模式通过引入代理对象,实现了对目标对象访问的控制和功能扩展,为软件开发提供了一种灵活、强大的解决方案。无论是静态代理还是动态代理,都有其适用的场景和特点。在实际开发中,当遇到需要控制对象访问、扩展对象功能、隐藏对象细节等需求时,代理设计模式是一个不错的选择。但同时也要注意其带来的系统复杂度增加和性能开销问题,合理运用该模式,让代码更加优雅和高效。