代理模式

Post on Sep 04, 2021 by Wei Lin

介绍

代理解决的问题是当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理。

代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

模式角色

(1) 抽象主题 (Subject)

​ 定义代理类和真实主题的公共对外方法,使得代理可以代替实际对象使用。


(2) 真实主题(RealSubject)

​ 该类可以被称为被委托类或被代理类,该类定义了代理对象所表示的真实对象,实现了Subject接口,而Client端通过代理类间接的调用的真实主题类中的方法,由其执行真正的业务逻辑。


(3) 代理类(Proxy)

​ 也被称为委托类或代理类,该类中持有一个真实主题类的引用,同样实现了Subject接口,在其实现的接口方法中调用RealSubject类中相应的接口方法,以此起到代理的作用。


(4) 客户端(Client)

​ 使用代理类和主题接口完成一些工作。


代理模式结构图如下:

代理模式.png

静态代理与动态代理

静态代理中代理者的代码由程序员编写或通过一些自动化工具生成固定的代码再对其进行编译,也就是说在代码运行前代理类的class编译文件就已存在。

而动态代理则是通过反射机制动态地生成代理者的对象,也就是说我们在code阶段压根就不需要知道代理是谁,而是在执行阶段决定。 而Java也给我们提供了一个便捷的动态代理接口 InvocationHandler,实现该接口需要重写其调用方法invoke。

举一个例子,小明想要提起诉讼:

  • 静态代理:小明提起诉讼,于是需要找一个诉讼律师,代小明执行提交申请、举证、辩护等流程,
  • 动态代理:小明临时改变主意,只想进行一些咨询,但之前的律师不提供咨询服务,于是小明只得再找一个咨询律师。

通过以上例子就会发现,每次更改被代理类的接口(即小明有新的需求),都需要更换代理类,这就是静态模式的缺点,只能为给定接口下的实现类做代理,如果接口不同就需要定义不同的代理类。随着系统的复杂度增加,就会很难维护这么多代理类和被代理类之间的关系,这时动态代理就应运而生。

当需要频繁地更改接口(被代理类),更换代理类时,采用动态代理是一个更好的选择,动态代理可以通过一个代理类来代理N多个被代理类,它在更换接口时,不需要重新定义代理类,因为动态代理不需要根据接口提前定义代理类,它把代理类的创建推迟到代码运行时来完成。

静态代理代码示例

示例代码来自《Android源码设计模式解析与实战》第18章

动态代理代码示例

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy implements InvocationHandler {
    private Object obj; //被代理的类引用

    public DynamicProxy(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //调用被代理类对象的方法,调用之前代理类也可以做一些其它操作
        Object result = method.invoke(obj, args);
        return result;
    }

    public static void main(String[] args) {
        //构造一个小民……
        ILawsuit xiaomin = new XiaoMin();
        //构造一个动态代理
        DynamicProxy proxy = new DynamicProxy(xiaomin);
        //获取被代理类小民的ClassLoader
        ClassLoader loader = xiaomin.getClass().getClassLoader();
        //动态构造一个代理者律师
        ILawsuit lawyer = (ILawsuit) Proxy.newProxyInstance(loader, new Class[]{ILawsuit.class}, proxy);
        //律师提交诉讼申请
        lawyer.submit();
        //律师进行举证
        lawyer.burden();
        //律师代替小民进行辩护
        lawyer.defend();
        //完成诉讼
        lawyer.finish();
    }
}

DynamicProxy是实现InvocationHandler接口的动态代理类,并且每个代理类(DynamicProxy)的实例都关联到了实现该接口的动态代理类调用处理程序中。当通过动态代理对象(由Proxy.newProxyInstance()创建)调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。

newProxyInstance()是java.lang.reflect.Proxy类中的一个静态方法,作用是生成代理对象,是通过反射在运行时生成的。

static Object newProxyInstance(ClassLoader var0, Class<?>[] var1, InvocationHandler var2){}

三个参数分别为:

  • 参数var0:被代理类的类加载器;
  • 参数var1:代理类与被代理类的共同接口类型;
  • 参数var2:事件处理器;
  • 返回对象:返回一个指定接口的代理类实例,该接口可以将方法调用指派到实际的实现类中。

应用场景

(1) 间接访问对象

​ 当不想访问某个对象或访问某个对象存在困难时,就可以为这个对象创建一个代理,通过代理来间接的访问这个对象。


(2) 控制访问权限

​ 如果原始对象有不同的访问权限,可以使用代理控制对原始对象的访问,保护原始对象。


(3) 执行附加操作

​ 在访问原始对象时执行一些自己的附加操作;


(4) 隐藏访问细节

​ 为某个对象在不同的内存地址空间提供局部代理,使得系统可以将服务端的实现隐藏,客户端不必考虑服务端的存在,例如Android中的Binder。

优缺点

(1) 静态代理的优点

  • 作为客户端和被代理类之间的中介,起到了保护被代理类的作用;
  • 通过接口对代理类和被代理类进行解耦,降低了系统的耦合度。


(2) 静态代理的缺点

  • 如果真实对象需求有变动,就需要更换代理类,所以代理类只能为特定的实现类做代理。随着需求的增多,就需要维护大量代理类和被代理类之间的关系。


(3) 动态代理的优点:

  • 代理类在程序运行时由反射自动生成,无需我们手动编写代理类代码,简化编程工作;
  • 动态代理可以通过一个代理类来代理N多个被代理类,当真实类变动时时,不需要重新定义代理类。


(4) 动态代理的缺点:

  • 动态代理只能代理实现了接口的类,而不能代理实现抽象类的类;
  • 通过反射调用被代理类的方法,效率低。

Android中的应用-Binder

如Binder通信方式,就是典型的代理模式。

以ActivityManagerService为例:

  • IActivityManager类为公共接口;
  • ActivityManagerService是具体实现类;
  • IMyService.Stub.Proxy为代理类;
  • ActivityManager为客户端,调用AMS中的方法。

​ ActivityManager -> IActivityManager.Stub.Proxy -> IActivityManager.Stub -> ActivityManagerService(继承并实现IMyService.Stub)

IActivityManager.Stub是个抽象类,其并不处理过多的具体逻辑,大部分具体逻辑的实现都由其子类ActivityManagerService承担,所以具体实现类应该为ActivityManagerService 而非 IActivityManager.Stub。