设计模式——代理模式

218 阅读7分钟

一、概述

什么是代理?就比如说我们朋友圈中有专门做海外代购朋友,我们自己不能出国去买,就只能通过代购的方式(网购也算),所以他就是代理的角色;打官司需要请律师,而律师也是一个代理的角色,全权交给律师来进行诉讼;我们委托别人代购的东西到了,没时间去取快递,就可以让朋友代理取,朋友也是扮演的代理的角色。通过简单的例子我们就明白什么是代理模式。代理模式提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上增强额外的功能操作,即扩展目标对象的功能。

代理模式的关键点是代理对象与目标对象(被代理对象)。代理对象是对目标对象的扩展,并会调用目标对象。代理模式也分为静态代理和动态代理。

二、使用

1、静态代理

静态代理通常是定义一个接口或者父类,目标对象与代理对象一起实现相同的接口或者是继承相同父类。这里说通常,是因为我们要对代理对象进行一些约束,比如朋友代理取快递,我们只需要让他代理取就行,而不需要拆快递,或者使用我们的东西;当然如果快递有损坏,或者一些异常的情况,我们也需要委托朋友代理退货的操作,因此代理的操作可能会有很多,所以写一个共同的接口或者父类更加的直观,也更好的理解静态代理。

下面我们还是以律师诉讼为例,来做具体说明。首先我们写一个诉讼接口,里面包含各种流程,当事人和代理律师都需要实现其中的方法。

/**
 * 诉讼接口
 */
public interface LawSuit {

    String TAG = "XXX";

    //开场
    void start();
    //陈述
    void statement();
    //提供证据
    void provideEvidence();
    //结束
    void end();
}

其次是当事人实现了诉讼接口,在抽象的方法中实现当事人自己的逻辑。

/**
 * 当事人
 */
public class Litigant implements LawSuit {

    @Override
    public void start() {
        Log.e(TAG, "当事人说:各位好!法官大人好!");
    }

    @Override
    public void statement() {
        Log.e(TAG, "当事人说:被告恶意碰瓷");
    }

    @Override
    public void provideEvidence() {
        Log.e(TAG, "当事人说:我有监控证据");
    }

    @Override
    public void end() {
        Log.e(TAG, "当事人说:我已经说完了");
    }
}

接下来就是代理律师了,也实现了诉讼接口,并且持有了当事人的引用,可以看到,在实现的方法中调用了当事人的相关方法。

/**
 * 代理律师
 */
public class Lawyer implements LawSuit {

    private Litigant litigant;

    public Lawyer(Litigant litigant){
        this.litigant = litigant;
    }

    @Override
    public void start() {
        litigant.start();
        Log.e(TAG, "代理律师说:各位好!法官大人好!");
    }

    @Override
    public void statement() {
        Log.e(TAG, "代理律师说:我方开始陈述");
        litigant.statement();
        Log.e(TAG, "代理律师说:我方陈述完毕");
    }

    @Override
    public void provideEvidence() {
        Log.e(TAG, "代理律师说:我方开始提供证据");
        litigant.provideEvidence();
        Log.e(TAG, "代理律师说:我方已经提供完所有证据");
    }

    @Override
    public void end() {
        litigant.end();
        Log.e(TAG, "代理律师说:我方已经说完了");
    }
}

最后进行测试,打印输出如下图所示。可以在代理律师的方法中,我们可以看到,在调用当事人类中的方法时,可以在之前或者之后扩展自己的逻辑。

Litigant litigant = new Litigant();
Lawyer lawyer = new Lawyer(litigant);
lawyer.start();
lawyer.statement();
lawyer.provideEvidence();
lawyer.end();

小结静态代理,通过上面的代码测试,使用静态代理其实很简单。调用代理类的方法时,其实最后真正执行的还是目标对象的方法,代理类可以在不修改目标对象的功能下,可以对目标对象功能的扩展。当然有一些缺陷就是,当共有接口发生改变时,代理对象和目标都会受到影响;当代理类过多时,也会导致类的数量增加,不容易维护。所以为了解决这些问题,我们就需要用到动态代理。

2、动态代理

动态代理不需要使用共有接口,而代理对象的生成需要用到JDK中的java.lang.reflect.InvocationHandler接口,以及java.lang.reflect.Proxy这个类中的newProxyInstance静态方法。

那么接下来就用代码说明。首先我们需要创建一个类来实现InvocationHandler接口,这个接口只有一个invoke方法。在类中定义了一个获取代理对象的方法,这个方法中用到了newProxyInstance这个关键,下面分别对newProxyInstance方法中的几个参数说明一下:

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的;
  • Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型;
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

然后对InvocationHandler接口中的invoke方法参数说明一下:

  • Object proxy:代理对象,就是getProxy方法生成的对象;
  • Method method:当前调用的方法;
  • Object[] args:调用当前方法传入的参数。
/**
 * 动态代理
 */
public class DynamicProxy implements InvocationHandler {

    private static final String TAG = "XXX";
    private Object target;

    /**
     * 建立代理对象和目标对象的代理关系,并返回代理对象
     * @param target 目标对象
     * @return 代理对象
     */
    public Object getProxy(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("statement".equals(method.getName())){
            Log.e(TAG, "代理律师说:我方开始陈述");
        }else if("provideEvidence".equals(method.getName())){
            Log.e(TAG, "代理律师说:我方开始提供证据");
        }

        //以上就是在调用目标对象之前的逻辑
        Object obj = method.invoke(target,args);
        //以下就是调用目标对象之后的逻辑

        if("start".equals(method.getName())){
            Log.e(TAG, "代理律师说:各位好!法官大人好!");
        }else if("statement".equals(method.getName())){
            Log.e(TAG, "代理律师说:我方陈述完毕");
        }else if("provideEvidence".equals(method.getName())){
            Log.e(TAG, "代理律师说:我方已经提供完所有证据");
        }else if("end".equals(method.getName())){
            Log.e(TAG, "代理律师说:我方已经说完了");
        }
        return obj;
    }
}

在invoke方法中,我们可以看到调用Object obj = method.invoke(target,args);语句,这里有反射相关的使用,关于反射的用法可以参照其他的博文,这里不再过多说明。当执行到这条语句时,实际上是调用目标对象的方法(也就是我们的当事人类中的方法)

,在调用目标对象方法前后,可以扩展其他的逻辑。这里只是简单的判断了方法名称,根据执行不同的方法,使得代理对象打印不同的信息。最后进行测试,得到打印输出如下所示。

DynamicProxy dynamicProxy = new DynamicProxy();
LawSuit lawSuit = (LawSuit) dynamicProxy.getProxy(new Litigant());
lawSuit.start();
lawSuit.statement();
lawSuit.provideEvidence();
lawSuit.end();

小结动态代理,代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。动态代理的核心就是InvocationHandler接口和Proxy.newProxyInstance静态方法,使用起来也并不复杂。

三、总结

代理模式也是我们在平常用的较多的一种模式。当然相对于动态代理来说,静态代理要用的更多一些,使用起来也很简单方便。在Android中代理模式是用到很多的,比如跨进程通信、Activity的启动这些都会涉及到的服务端(service)和客户端(client)进行交互,当然使用起来肯定就比我们的例子要复杂的多的多,有兴趣的也可以看看这一系列的文章。

github地址:github.com/leewell5717…

四、参考

Java设计模式之动态代理

Java的三种代理模式