代理模式(Proxy Pattern)

15 篇文章 0 订阅
6 篇文章 0 订阅

《设计模式的艺术》 刘伟 著 读书笔记

静态代理

由于某些原因,客户端不能或不像直接访问某个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,这种设计模式就是代理模式。

代理模式:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

常用的代理模式:

  • 远程代理(Remote Proxy):在电脑A想用电脑B的对象instance,就需要在A中做一个对电脑B中对象instance的引用,这个引用就是“代理”(Proxy)。
  • 虚拟代理(Virtual Proxy):对于资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。比如微信朋友圈中的图片,默认只加载缩略图,点开才加载大图。
  • 保护代理(Protect Proxy):控制一个对象的访问,比如权限控制,对不同用户提供不同级别的使用权限,在对象访问时先进行权限判定。
  • 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时存储空间,以便多个客户端可以共享这些结果。
  • 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如记录对象被调用次数。

类图如下:
这里写图片描述
客户端有一个Subject接口的引用,RealSubject和Proxy都实现这个接口,RealSubject类就是被代理的类,在客户端中实际使用的是Proxy类的对象,其成员变量中有被代理的对象的引用,在Proxy类的实现Subject接口的方法中,执行如下代码

    before();
    realSubject.dosomething();
    after();

其中before和after就是额外添加的职能了,比如权限验证,日志记录等。

代理模式和装饰模式有些类似,但代理模式是添加全新的职责,如上面所说的,与原职责不再同一个问题域,而装饰模式是为具体构建类添加相关职责,比如拓展算法类,给文本框加滚动条。代理模式是控制对对象的访问,而装饰模式是为对象动态增加功能。


动态代理

但是,如果我们有很多个类需要代理,不就每个要代理的类都要写个接口然后写个代理对象?这种代理成为静态代理。
而动态代理就是能让系统在运行时动态创建代理类,我们只需要为每个类写接口和实现类,代理类只需要写一次。。
Java中有这么一个方法

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandlder h)

这个方法返回的一个动态创建的代理类。那么怎么与接口(Subject)以及被代理的类(RealSubject)联系起来呢?该方法第一个参数表示代理类的类加载器,就是Subject的classLoader. 第二个参数是代理类所实现的接口列表(到时要调用接口定义的方法),第三个参数表示所指派的调用处理程序类(就是在这个类写权限认证,日志记录的东西)。
而InvocationHandler是一个接口,需要实现方法:

public Object invoke(Object proxy, Method method, Object[] args)

第一个参数是代理类的实例,第二个参数表示调用代理类的方法,第三个参数表示方法的参数数组。

来个实际例子吧,

public interface Item {
    int getNum();
    void printNum();
}
public class RealSubjectB implements Item {

    @Override
    public int getNum() {
        return 0;
    }

    @Override
    public void printNum() {

    }
}

我要添加一个日志记录功能,对所有方法都输出当前时间,并对调用了get开头的方法输出提示。

public class LogHandler implements InvocationHandler {

    private Object objectBeProxied;
    //诸如需要提供代理的realSubjcet
    public LogHandler(Object o){
        objectBeProxied=o;
    }
    public void before(){
        System.out.println(new Date().toString());
    }
    public  void invokeGet(){
        System.out.println("调用了get开头的方法");
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        if(method.getName().matches("get.+")){
            invokeGet();
        }
        return method.invoke(objectBeProxied,args);
    }
}
public class Client {
    public static void main(String[] args){
        Item realSubject=new RealSubjectB();
        InvocationHandler handler=new LogHandler(realSubject);
        Item proxy= (Item) Proxy.newProxyInstance(Item.class.getClassLoader(),new Class[]{Item.class},handler);
        proxy.getNum();
        System.out.println("--------");
        proxy.printNum();
    }
}

输出

Fri Dec 23 00:38:51 CST 2016
调用了get开头的方法
--------
Fri Dec 23 00:38:51 CST 2016

但是,这里有个疑惑,如果realSubject实现了多个接口,且代理类想调用不同接口的方法怎么办?这样在newProxyInstance的第一个参数写哪个接口都不合适啊?写了A接口就不能用B接口的方法。而如果写一个抽象类实现所有接口,然后第一个参数写抽象类,就会报ClassCastException。难道直接在第一个参数写realSubject而不是Subject?似乎也没什么不妥啊?

上面的问题找到解决办法了,首先newProxyInstance的第一个参数只是指定了如何定义而已,而第二个参数是返回的对象实现了哪些方法。
如何,如果要调用另一个接口的方法,只需要:

        Item realSubject=new RealSubjectB();
        InvocationHandler handler=new LogHandler(realSubject);
        Item proxy= (Item) Proxy.newProxyInstance(RealSubjectB.class.getClassLoader(),new Class[]{Item.class,Category.class},handler);
        proxy.getNum();
        System.out.println("--------");
        proxy.printNum();
        System.out.println("--------");
        //下面两句是新增代码,Category是新的接口,里面有getI的方法。
        Category proxy1= (Category) proxy;
        proxy1.getI();

竟然可以直接转型!应该是newProxyInstance第二个参数写了Category 这个接口的缘故!
其实说到底,前两个参数只是定义,实际对象还是在第三个参数传入。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值