前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式 ——— 代理模式

设计模式 ——— 代理模式

作者头像
tomas家的小拨浪鼓
发布2018-06-27 14:16:47
4480
发布2018-06-27 14:16:47
举报
文章被收录于专栏:木木玲木木玲

意图

为其他对象提供一种代理以控制对这个对象的访问

代理模式通过代理目标对象,把代理对象插入到客户和目标对象之间,从而为客户和目标对象引入一定的间接性,正是这个间接性,给了代理对象很多的活动空间,代理对象可以在调用具体的目标对象前后,附加很多操作,从而实现新的功能或是扩展目标对象的功能,更狠的是,代理对象还可以不去创建和调用目标对象,也就是说,目标对象被完全代理掉了,或是被替换掉了。

功能

代理模式是通过创建一个代理对象,用这个代理对象去代表真实的对象,客户端得到这个代理对象过后,对客户端没有什么影响,就跟得到了真实对象一样来使用。

当客户端操作这个代理对象的时候,实际上功能最终还是会由真实的对象来完成,只不过是通过代理操作的,也就是客户端操作代理,代理操作真正的对象。

结构

代理模式结构图

  • Proxy:代理对象: a) 实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象 b) 保存一个指向具体目标对象的引用,可以在需要的时候调用具体的目标对象,可以控制对具体目标对象的访问,并可能负责创建和删除它。 c) 其他的功能依赖于代理的类型
  • Subject:目标接口 定义RealSubject和Proxy的共用接口,这样就可以在任何使用具体目标对象的地方使用代理对象
  • RealSubject:具体的目标对象 具体的目标对象,真正实现目标接口要求的功能。

代理的分类

  • 虚代理: 根据需要来创建开销很大的对象,该对象只有在需要的时候才会被真正创建;
  • 远程代理: 为一个对象在不同的地址空间提供局部代表,这个不同的地址空间可以是在本机,也可以在其它机器上,在Java里面最典型的就是RMI技术;
  • Copy-on-Write代理: 在客户端操作的时候,只有对象确实改变了,才会真的拷贝(或克隆)一个目标对象,算是虚代理的一个分支; 在实现Copy-on-write时必须对实现进行引用计数。拷贝代理仅会增加引用计数。只有当用户请求一个修改该实体的操作时,代理才会真正的拷贝它。在这种情况下,代理还必须减少实体的引用计数。当引用的数目为零时,这个实体将被删除。
  • 保护代理: 控制对原始对象的访问,如果有需要,可以给不同的用户提供不同的访问权限,以控制他们对原始对象的访问;
  • 智能指引: 在访问对象时执行一些附加操作,比如: a) 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它; b) 当第一次引用一个持久对象时,将它装入内存。 c) 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。

Java中的代理

Java对代理模式提供了内建的支持,在java.lang.reflect包下面,提供了一个Proxy的类和一个InvocationHandler的接口。

通常把前面自己实现的代理模式,称为Java的静态代理。这种实现方式有一个较大的缺点,就是如果Subject接口发生变化,那么代理类和具体的目标实现都要变化,不是很灵活,而使用Java内建的对代理模式支持的功能来实现则没有这个问题。

通常把使用Java内建的对代理模式支持的功能来实现的代理称为Java的动态代理。动态代理跟静态代理相比,明显的变化是:静态代理实现的时候,在Subject接口上定义很多的方法,代理类里面自然也要实现很多方法;而动态代理实现的时候,虽然Subject接口上定义了很多方法,但是动态代理类始终只有一个invoke方法。这样当Subject接口发生变化的时候,动态代理的接口就不需要跟着变化了。

注意:Java的动态代理目前只能代理接口,基本的实现是依靠Java的反射机制和动态生成class的技术,来动态生成被代理的接口的实现对象。如果要实现类的代理,可以使用cglib、Javassist。

相关模式

  • 代理模式 VS 适配器模式 相同点:它们都为另一个对象提供间接性的访问,而且都是从自身以外的一个接口向这个对象转发请求。 不同点:但是从功能上,两个模式是不一样的。适配器模式主要用来解决接口之间不匹配的问题,它通常是为所适配的对象提供一个不同的接口;而代理模式会实现和目标对象相同的接口。
  • 代理模式 VS 装饰模式 相同点:实现上是类似的,都是在转调其它对象的前后执行一定的功能。但是它们的目的和功能都是不同的。 不同点:装饰模式的目的是为了让你不生成子类就可以给对象添加职责,也就是为了动态的增加功能;而代理模式的主要目的是控制对对象的访问。

示例

  • 静态代理

Subject:

代码语言:javascript
复制
public abstract class Subject
{
    public abstract void request();
}

RealSubject:

代码语言:javascript
复制
public class RealSubject extends Subject
{
    public void request()
    {
        System.out.println("From real subject.");
    }
}

ProxySubject:

代码语言:javascript
复制
public class ProxySubject extends Subject
{
    private RealSubject realSubject; //代理角色内部引用了真实角色
    
    public void request()
    {
        this.preRequest(); //在真实角色操作之前所附加的操作
        
        if(null == realSubject)
        {
            realSubject = new RealSubject();
        }
        
        realSubject.request(); //真实角色所完成的事情
        
        this.postRequest(); //在真实角色操作之后所附加的操作
    }
    
    private void preRequest()
    {
        System.out.println("pre request");
    }
    
    private void postRequest()
    {
        System.out.println("post request");
    }
}

客户端使用:

代码语言:javascript
复制
public class Client
{
    public static void main(String[] args)
    {
        Subject subject = new ProxySubject();
        
        subject.request();
    }
}
  • 动态代理

Subject:

代码语言:javascript
复制
public interface Subject
{
    public void request();
}

RealSubject:

代码语言:javascript
复制
public class RealSubject implements Subject
{
    public void request()
    {
        System.out.println("From real subject!");
    }

}

DynamicSubject:

代码语言:javascript
复制
/**
 * 该代理类的内部属性是Object类型,实际使用的时候通过该类的构造方法传递进来一个对象
 * 此外,该类还实现了invoke方法,该方法中的method.invoke其实就是调用被代理对象的将要
 * 执行的方法,方法参数是sub,表示该方法从属于sub,通过动态代理类,我们可以在执行真实对象的方法前后
 * 加入自己的一些额外方法。
 *
 */

public class DynamicSubject implements InvocationHandler
{
    private Object sub;
    
    public DynamicSubject(Object obj)
    {
        this.sub = obj;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
    {
        System.out.println("before calling: " + method);
        
        method.invoke(sub, args);
        
        System.out.println(args == null);
        
        System.out.println("after calling: " + method);
        
        return null;
    }
}

注意:invoke的第一个参数Object proxy指的是动态代理的那个对象,也就是我们下面通过Proxy.newProxyInstance(...)方法构建的动态代理对象。

客户端使用:

代码语言:javascript
复制
public class Client
{
    public static void main(String[] args)
    {
        RealSubject realSubject = new RealSubject();

        InvocationHandler handler = new DynamicSubject(realSubject);

        Class<?> classType = handler.getClass();

        Subject subject = (Subject) Proxy.newProxyInstance(classType
                .getClassLoader(), realSubject.getClass().getInterfaces(),
                handler);

        subject.request();

        System.out.println(subject.getClass());

    }
    
}

① static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h): 返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法) 这句代码生成的实例,既不是RealSubject实例,也不是DynamicSubject实例。生成的是运行期间动态所生成的实例。 ② 所谓Dynamic Proxy是这样一种class: 它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。(Class的实例就可以是任何一个接口)当然,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler(InvocationHandler h),由它接管实际的工作 ③ subject.request(); 不管调用生成代理对象(subject只是个代理)的任何一个方法,流程都会立刻转换到了handler里的invoke方法。

所以我们在使用动态代理的时候,一般需要通过Proxy的静态方法来生成一个动态代理类,然后我们就可以使用这个动态代理类来替代真是的类了。 动态代理类可以实现真实类所实现的所有接口类,同时动态代理类的构建还需要我们传入一个InvocationHandler。InvocationHandler对象会真实替我们完成代理的操作,也就是说我们调用代理类的某个方法后,最终都会转到该对象中通过invoke方法来实现。

参考

《Head First 设计模式》 《设计模式:可复用面向对象软件的基础》 《研磨设计模式》 圣思园 Java SE

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.09.10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 意图
  • 功能
  • 结构
  • 代理的分类
  • Java中的代理
  • 相关模式
  • 示例
    • 参考
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档