代理模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用中介,来间接的调用实际的对象,这个中介就是代理对象。
为什么要采用这种间接的形式来调用对象呢?——一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。在现实生活中,这种情形非常的常见,比如请一个代购帮我们从海外买东西。
消费者虽然买的是SKII的化妆水,确实通过实体店店买到,但并不是自己直接去国外买的,而是通过代购买的,这就是代理模式。
//代理模式
//接口
interface Subject{
void buy();
}
//真实主题类
class RealSubject implements Subject{
@Override
public void buy() { System.out.println("买一个SKII化妆水"); }
}
//代理类
class AgentSubject implements Subject{
private RealSubject realSubject;
public AgentSubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void buy() {
money();
this.realSubject.buy();
OK();
}
//买之前需要取钱
private void money(){ System.out.println("取钱准备买"); }
//买完后需要给真实要买的客户快递过去
private void OK(){ System.out.println("OK买好,准备回国发货"); }
}
public class AgentFun {
public static void main(String[] args) {
Subject subject = new AgentSubject(new RealSubject());
subject.buy();
}
}
通过上面的代理栗子,我们可以看出代理模式的特点,代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。但是也有缺点,每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。
主要优点:
主要缺点:
那先要说一下代理模式中的三种角色了。
抽象角色:声明真实对象和代理对象的共同接口。
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
代理模式的一个好处就是对外部提供统一的接口方法,而代理类在接口中实现对真实类的附加操作行为,从而可以在不影响外部调用情况下,进行系统扩展。也就是说,我要修改真实角色的操作的时候,尽量不要修改他,而是在外部在“包”一层进行附加行为,即代理类。
例如:接口A有一个接口方法fun(),真实角色:RealA实现接口A,则必须实现接口方法fun()。客户端Client调用接口A的接方法fun()。现在新需求来了,需要修改RealA中的fun()的操作行为。怎么办呢?如果修改RealA就会影响原有系统的稳定性,还要重新测试。这是就需要代理类实现附加行为操作。创建代理ProxyA实现接口A,并将真实对象RealA注入进来。ProxyA实现接口方法fun(),另外还可以增加附加行为,然后调用真实对象的fun()。从而达到了“对修改关闭,对扩展开放”,保证了系统的稳定性。我们看客户端Client调用仍是接口A的接口方法fun(),只不过实例变为了ProxyA类了而已。也就是说代理模式实现了ocp原则。
当我们需要使用的对象很复杂或者需要很长时间去构造,这时就可以使用代理模式(Proxy)。例如:如果构建一个对象很耗费时间和计算机资源,代理模式(Proxy)允许我们控制这种情况,直到我们需要使用实际的对象。一个代理(Proxy)通常包含和将要使用的对象同样的方法,一旦开始使用这个对象,这些方法将通过代理(Proxy)传递给实际的对象。 一些可以使用代理模式(Proxy)的情况:
一个对象,比如一幅很大的图像,需要载入的时间很长。
一个需要很长时间才可以完成的计算结果,并且需要在它计算过程中显示中间结果
一个存在于远程计算机上的对象,需要通过网络载入这个远程对象则需要很长时间,特别是在网络传输高峰期。
一个对象只有有限的访问权限,代理模式(Proxy)可以验证用户的权限
代理模式(Proxy)也可以被用来区别一个对象实例的请求和实际的访问,例如:在程序初始化过程中可能建立多个对象,但并不都是马上使用,代理模式(Proxy)可以载入需要的真正的对象。这是一个需要载入和显示一幅很大的图像的程序,当程序启动时,就必须确定要显示的图像,但是实际的图像只能在完全载入后才可以显示!这时我们就可以使用代理模式(Proxy)。
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
动态代理简单实现
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//核心操作类
interface ISubject{
void eat(int count);
}
//真实主题类
class RealISubject implements ISubject{
@Override
public void eat(int count) {
System.out.println("吃"+count+"元自助");
}
}
//动态代理类
class ProxyISubject implements InvocationHandler {
private Object obj;
public Object bind(Object obj){
//保护真实主题类
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.befor();
//反射调用方法
Object ret = method.invoke(this.obj,args);
this.after();
return ret;
}
public void befor(){
System.out.println("先挑选地方");
}
public void after(){
System.out.println("付钱,心满意足~~");
}
}
public class ProxyTest {
public static void main(String[] args) {
ISubject subject = (ISubject) new ProxyISubject().bind(new RealISubject());
subject.eat(90);
}
}