专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

代理模式探析与应用


定义

是Java中的一种重要的设计模式。它为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。代理模式分为两种,静态代理和动态代理。

作用

  • 某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和目标对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  • 代理类除了是客户类和目标类的中介之外,还可通过给代理类增加额外的功能来拓展目标类的功能。这样子我们只需要修改代理类,而不需要再修改目标类,从而符合代码设计的开闭原则。

演示案例

其实直接说定义是非常枯燥乏味的,我们还是通过一个日常生活中的常见例子来探析代理模式吧。大家都听过海淘,代购吧,这些行为的本质其实就是消费者,通过中间商或者中间人,从海外购买货品的一种消费行为。对应到我们的代理模式来说,货品其实就是被代理对象,中间商是代理对象,消费者则是被调用对象。

69_1.png

代理模式(静态代理)类图如下:

69_2.png

在这里,假如有个消费者想购买国外最新款的PS4,它需要通过中间商代理,通过中间商去国外采购,工厂生产出来后,再通过中间商将PS4快递给消费者。对比上图我们可以清晰地认识到,PS4就是真实对象,也就是被代理对象,中间商就是代理对象,消费者是调用者。由于真实对象和代理对象需要实现共同的接口,也就是需要一个抽象对象。所以这里我们先定义一个游戏工厂接口,如下:

/**
 * 游戏工厂,包含所有游戏的生产
 */
public interface GameFactory {
    /**
     * 生产方法
     * @return
     */
    String make();
}

然后定义Ps4工厂,实现GameFactory接口,在这里它就是真实对象。

/**
 * Ps4工厂
 */
public class Ps4Factory implements GameFactory {

    @Override
    public String make() {
        return "ps4";
    }
}

接着定义中间商代理类,由类图可知,它也要实现GameFactory接口,在这里它就是代理对象。

/**
 * 中间商代理
 */
public class MiddlemanProxy implements GameFactory {

   GameFactory factory;

    public MiddlemanProxy(GameFactory factory) {
        this.factory = factory;
    }

    @Override
    public String make() {

        doSomethingBefore();
        String toy = factory.make();
        System.out.println("生产的玩具:"+toy);
        doSomethingAfter();
        return null;
    }

    /**
     * 售后服务
     */
    private void doSomethingAfter() {
        System.out.println("物流配置,用户售后");
    }

    /**
     * 售前服务
     */
    private void doSomethingBefore() {
        System.out.println("接收订单,生产排期");
    }
}

我们可以看到,它除了实现GameFactory接口,在构造时,也需要一个GameFactory实例(由类图可知),然后在make()方法的内部,又通过这个GameFactory实例,调用GameFactory实例本身的make()方法,还在调用前后,做了其他处理。由于Ps4Factory本身也实现了GameFactory接口,所以根据多态的思想,调用者在构造MiddlemanProxy实例时,只要将Ps4Factory实例传入,通过MiddlemanProxy实例调用自身的make()方法的方式,就实现了代理模式。

接下来我们看看消费者(调用者)如何调用代理:

/**
 * 消费者
 */
public class Consumer {

    public static void main(String[] args) {

        //消费者要购买ps4,要通过代理
        GameFactory ps4Factory = new Ps4Factory();
        MiddlemanProxy middlemanProxy = new MiddlemanProxy(ps4Factory);
        middlemanProxy.make();
    }
}

运行结果如下:

接收订单,生产排期 生产的玩具:ps4 物流配置,用户售后

现在,我们总结一下,类图与我们这个案例的各个类的对应关系:

类图 案例
抽象对象接口 Subject GameFactory
真实对象实现类 RealSubject Ps4Factory
代理对象实现类 Proxy MiddlemanProxy
真实对象执行方法 RealSubject.play() Ps4Factory.make()
代理对象执行方法 Proxy.play() MiddlemanProxy.make()

上面的案例其实是代理模式的一种:静态代理。除了静态代理,还有动态代理,下面我们来了解一下他们的区别,同时也会举另外一个例子来说明如何实现动态代理。

代理模式的种类

静态代理

需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者继承相同的父类。

动态代理

代理类在程序运行时创建的代理方法被成为动态代理。代理类不是在Java代码中定义的。而是在运行时根据在Java代码中的“指示”动态生成的。

我们按照上面的例子来进一步拓展。上面的例子,我们新建了一个游戏工厂的接口,代理对象(中间商)和被代理对象(游戏)都实现了这个接口。现在问题来了,如果这个中间商一直都代理游戏等商品,每当有新的游戏,只要创建新的游戏类,实现游戏工厂接口就行,而不会改动已有的代码。但是像人一样,生意总是越做越大的,随着需求种类的增加,代理对象可能也想代理不同种类的对象,这时候代理类就要实现多个工厂接口,比如实现一个球鞋工厂接口;或者代理相同种类的不同流程,比如,我们上面的make()方法表示制造,如果想代理设计,是不是要在游戏工厂接口中增加设计的方法,既然接口都定义了抽象方法,那么所有的实现类都要实现这个抽象方法。所以功能拓展时,又要涉及到很多已有接口和类的修改,这就违背了开闭原则。那么,我们能不能在运行时,才动态地生成代理对象,而不用写死一个代理类?答案是可以的,这就是我们接下来要介绍的另外一种设计模式——动态代理。

动态代理案例

JDk已经提供了可以实现动态代理的接口InvocationHandler,只要实现了这个接口,设置目标对象,重写invoke()方法,就可以实现动态代理。动态代理的本质是基于反射的。想要深究原理的话,可以看这篇博客:动态代理原理解析

public class MiddlemanProxy2 implements InvocationHandler {

    //1、定义目标对象,即被代理对象
    private Object target;

    public MiddlemanProxy2(Object target){
        this.target = target;
    }

    //2、通过反射取得代理对象
    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    //3、通过代理对象反射调用目标对象的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //这里可以自定义前置处理
        doSomethingBefore();
        //核心代码,反射,完成目标对象的方法的调用
        Object result = method.invoke(target, args);
        System.out.println(result);
        //这里可以自定义后置处理
        doSomethingAfter();
        return result;
    }

    /**
     * 售后服务
     */
    private void doSomethingAfter() {
        System.out.println("物流配置,用户售后");
    }

    /**
     * 售前服务
     */
    private void doSomethingBefore() {
        System.out.println("接收订单,生产排期");
    }
}

实现InvocationHandler这个接口时,最开始invoke()方法默认返回空,我们可以看到这个方法需要一个proxy,即代理对象,因此我们就要定义一个返回代理对象的getProxy()方法,而构建代理对象,肯定需要知道目标对象,因此我们定义了一个目标对象target的成员变量,通过构造方法传入。然后我们在getProxy()方法中,通过JDK的Proxy.newProxyInstance方法,传入目标对象的类加载器,接口数组,以及InvocationHandler类对象this,生产代理对象。最后我们重写了invoke方法,通过目标对象,反射调用了对应的方法,并且可以在调用前后,自定义相关的处理代码。接下来我们来看调用者代码:

/**
 * 消费者
 */
public class Consumer {

    public static void main(String[] args) {

        //消费者要购买ps4,要通过代理
        GameFactory ps4Factory = new Ps4Factory();
//        MiddlemanProxy middlemanProxy = new MiddlemanProxy(ps4Factory);
//        middlemanProxy.make();

        MiddlemanProxy2 middlemanProxy2 = new MiddlemanProxy2(ps4Factory);
        GameFactory proxy = (GameFactory)middlemanProxy2.getProxy();
        proxy.make();
    }
}

运行结果如下:

接收订单,生产排期
ps4
物流配置,用户售后

可以看出,他们得到了相同的结果。同样的,假如现在要代理书籍,我们只需要定义书籍工厂的接口和对应的实现类,生成代理对象时,传入书籍实现类对象即可。之前的目标类和代理类本身都不需要被修改,因为动态代理实际上是通过类而不是接口代理的,根本不需要跟目标类实现相同的接口,真正满足了开闭原则。 最后,我们来总结一下静态代理和动态代理的区别吧:

静态代理 动态代理
是否需要实现接口 需要 不需要
可接收的目标对象类 事先定义好的类型 动态指定的类型
实现方式 基于接口实现 基于类反射
特点 代码重复,耦合度高,不利于拓展 可代理任意类型对象,松耦合

最后,有需要的话,博客源码传送门~

文章永久链接:https://tech.souyunku.com/26748

未经允许不得转载:搜云库技术团队 » 代理模式探析与应用

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们