设计模式 ——— 代理模式

意图

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

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

功能

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

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

结构

代理模式结构图

  • 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:

public abstract class Subject
{
    public abstract void request();
}

RealSubject:

public class RealSubject extends Subject
{
    public void request()
    {
        System.out.println("From real subject.");
    }
}

ProxySubject:

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");
    }
}

客户端使用:

public class Client
{
    public static void main(String[] args)
    {
        Subject subject = new ProxySubject();
        
        subject.request();
    }
}
  • 动态代理

Subject:

public interface Subject
{
    public void request();
}

RealSubject:

public class RealSubject implements Subject
{
    public void request()
    {
        System.out.println("From real subject!");
    }

}

DynamicSubject:

/**
 * 该代理类的内部属性是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(...)方法构建的动态代理对象。

客户端使用:

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java编程

Java泛型详解

Java泛型(generics)是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具...

6360
来自专栏爱撒谎的男孩

设计模式之原型模式

1233
来自专栏Java爬坑系列

你不可不知的Java引用类型之——虚引用

当试图通过虚引用的get()方法取得强引用时,总是会返回null,并且,虚引用必须和引用队列一起使用。既然这么虚,那么它出现的意义何在??

633
来自专栏Django Scrapy

python面向对象

面向对象的一般概念: # Class 类 一个抽象 Object 对象 一个实例 封装: 在类中对数据的赋值 内部调用对外部是透明的 继承: 一个类可以...

3215
来自专栏wOw的Android小站

[设计模式]之十九:装饰模式

基本思路是,在ConcreateComponent做一些基本操作,然后创建装饰器,如果需要给对象增加新的特性,就把该对象放入对应的装饰器中。

282
来自专栏维C果糖

编程思想 之「多态、初始化顺序、协变返回类型」

在面向对象的编程语言中,有三个特性,分别为:封装、继承和多态。实现多态的前提是继承,多态的作用是消除类型之间的耦合关系。对于多态,我们常说的词有两个,分别为:向...

4214
来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础-21(02)总结字符流,IO流编码问题,实用案例必做一遍

C:把集合中的数据存储到文本文件 package cn.itcast_02; import java.io.BufferedWriter; import jav...

3584
来自专栏领域驱动设计DDD实战进阶

16-TypeScript装饰器模式

在客户端脚本中,有一个类通常有一个方法需要执行一些操作,当我们需要扩展新功能,增加一些操作代码时,通常需要修改类中方法的代码,这种方式违背了开闭的原则。 装饰器...

3024
来自专栏专注 Java 基础分享

Spring框架学习之高级依赖关系配置(一)

     上篇文章我们对Spring做了初步的学习,了解了基本的依赖注入思想、学会简单的配置bean、能够使用Spring容器管理我们的bean实例等。但这还只...

1719
来自专栏desperate633

设计模式之原型模式(Prototype 模式)引入原型模式原型模式的实例小结为什么需要使用原型模式

联想到浏览器中,如果我们生成了一个button实例,这个button实例经过一系列操作,携带了各种信息,比如button加颜色,加背景图,加文字,加事件等等。如...

552

扫码关注云+社区