在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
一、定义
为其他对象提供一种代理以控制对这个对象的访问。
二、解决的问题
在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,提供额外的处理或者不同的操作。
三、模式中的角色
- 抽象主题角色(Subject):声明真实对象和代理对象的共同接口;
- 代理角色(Proxy):代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
- 真实主题角色(RealSubject):代理角色所代表的真实对象,是我们最终要引用的对象。
四、模式解读
代理模式的类图如下所示
RealSubject通常是真正做事的对象,proxy会控制对RealSubject的访问。Proxy持有Subject的引用,所以必要时它可以将请求转发给Subject,在某些例子中,Proxy还负责RealSubject的创建和销毁。Procy和RealSubject都实现了Subject接口,任何用到RealSubject的地方都可以使用Proxy对象取代。
五、JDK涉及到的设计模式
JDK中体现:动态代理和远程方法调用RMI,以动态代理为例。所谓Dynamic Proxy是这样一种class,它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic
Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作,类图如下所示。
//抽象角色(之前是抽象类,此处应改为接口):
public interface Subject{
public void request();
}
//具体角色RealSubject:实现了Subject接口的request()方法。
public class RealSubject implements Subject {
public RealSubject() {
}
public void request() {
System.out.println("From real subject.");
}
}
//代理角色:
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;
public class DynamicSubject implements InvocationHandler {
private Object sub;
public DynamicSubject(Object sub) {
this.sub = sub;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before calling " + method);
method.invoke(sub,args);
System.out.println("after calling " + method);
return null;
}
}
该代理类的内部属性为Object类,实际使用时通过该类的构造函数DynamicSubject(Object sub)对其赋值;此外,在该类还实现了invoke方法,该方法中的“method.invoke(sub,args)” 其实就是调用被代理对象的将要被执行的方法,方法参数sub是实际的被代理对象,args为执行被代理对象相应操作所需的参数。通过动态代理类,我们可以在调用之前或之后执行一些相关操作。
客户端代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Client {
public static void main(String[] args) throws Throwable {
RealSubject realSubject = new RealSubject(); //在这里指定被代理类
InvocationHandler handler = new DynamicSubject(realSubject); //初始化代理类
Class clazz = realSubject.getClass();
Subject subject = (Subject) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),handler);
subject.request();
}
}
通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系。
六、模式总结
与其他模式比较
- 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
- 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
优点
- 1、透明调用被代理对象,无须知道复杂实现细节
- 2、 增加被代理类的功能
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
适用场景
- 1、远程代理(RemoteProxy),可以隐藏一个对象存在于不同地址空间的事实。也使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
- 2、虚拟代理(VirtualProxy), 允许内存开销较大的对象在需要的时候创建。只有我们真正需要这个对象的时候才创建。
- 3、写入时复制代理(Copy-On-Write
Proxy),用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止。是虚拟代理的一个变体。 - 4、保护代理(Protection-AccessProxy),为不同的客户提供不同级别的目标对象访问权限
- 5、缓存代理(CacheProxy), 为开销大的运算结果提供暂时存储,它允许多个客户共享结果,以减少计算或网络延迟。
- 6、防火墙代理(Firewall Proxy), 控制网络资源的访问,保护主题免于恶意客户的侵害。
- 7、同步代理(SynchronizationProxy),在多线程的情况下为主题提供安全的访问。
- 8、智能引用代理(Smar ReferenceProxy),当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
- 9、复杂隐藏代理(ComplexityHidingProxy),
用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称为外观代理(Façade
Proxy),这不难理解。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。