2021-10-15

代理模式和适配器模式的区别


前言

上次我们讲到了适配器模式,适配器模式实现目标接口,我们的适配者类想要实现目标接口中的目标方法,我们不是直接调用目标接口中的方法,这样适配者和目标接口会直接存在耦合,现在我们通过适配器,适配器实现了目标接口,而且能够生成适配者对象,适配器能,适配者对象就能实现目标方法。那么代理模式呢?我们了解到适配器模式底层调用了拦截器,就能够通过拦截进行匹配,那么拦截器的底层又是动态代理,所以说适配器是基于代理模式。代理模式会生成一个代理类,不直接去操作目标类,而是去使用代理类去操作目标类。而且代理模式即便需求变化,接口和方法名也不会变化,有点继承的味道。那么适配者模式协调需求差异,在实现了目标接口的情况下,更多的是创建新的适配者和与之对应的适配器,这种情况,方法名就会发生变化了,这就是java程序的美丽,不同的写法就是不同逻辑和实现方式的体现。


一、代理模式是什么?

代理模式是为其他类提供一种代理以控制对这个类的访问。我们不直接去接触目标类,而是直接操作代理类,代理类再去操作目标类。因为不直接接触目标类,因此我们可以在代理类的同名方法中添加或删除功能模块,而不用去修改目标类的原方法。
代理模式与适配器模式都分别有继承、接口方式实现的子分类模式。基于接口实现的代理模式称为静态代理模式、JDK(动态)代理模式,基于继承实现的代理模式称为Cglib(动态)代理模式。
当然了,我们刚刚说到适配器模式的底层是代理模式,那么适配器模式肯定也有基于接口和基于类的区别。基于接口(同时含类继承)实现的适配器模式称为类适配器模式,(只)基于继承(使用委托)实现的适配器模式称为类适配器模式。
因为使用的关系,我们平常很少使用静态代理,主要是实现动态代理,我们会在下面详细分析动态代理。

二、动态代理

1.JDK动态代理

JDK动态代理,主要是利用JDK提供的API动态生成代理类实例。我们不需要去写一个具体代理类,只需要一个代理工厂即可,JDK的系统API将在运行时动态生成代理类的字节码,并此字节码将加载到JVM中,在JVM中直接构建代理类(不需要将class文件读取到内存中),因此是一个动态创建代理的过程。
在实现JDK动态代理之前,我们分析一下其中常用的API:
java.lang.reflect.Proxy工具类
我们主要会用到其中的静态方法 static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
这个静态方法将能够动态构建一个实现了和目标类相同的接口的代理类(具体实现的接口个数,此方法中的参数interfaces有关),此方法的三个参数分别是目标类的类加载器loader、目标类实现的接口数组interfaces、 InvocationHandler类handler。

java.lang.reflect.InvocationHandler接口
此接口只有一个抽象方法 public Object invoke(Object proxy, Method method, Object[] args)throws Throwable
这个抽象方法的三个参数分别表示代理类proxy, 目标类方法method,目标类方法的参数args。这是一个回调方法,当我们调用代理类所实现的接口方法(和目标类相同的接口相同)时,java虚拟机会调用此方法。所以重写此方法非常重要,可以在此方法中添加我们所需要的增强效果。

先定义一个接口animal,给他提供一个move方法

public static interface Animal {
    void move();
}

然后定义一个目标类

public static class People implements Animal {
    String name;
    public people() {
    }
    public people(String name) {
        super();
        this.name = name;
    }
    @Override
    public void move() {
        System.out.println(name + "正在移动");
    }
}

定义动态代理的抽象工厂类

// 继承InvocationHandler,但不实现其接口方法,接口方法的具体实现交给抽象工厂类的子类去完成
public static abstract class ProxyFactory implements InvocationHandler {
    // 访问权限设置为保护级,其子类能继承到此成员变量
    protected Object target; // 目标对象
 
    /**
     * 构造方法
     *
     * @param target 目标对象
     */
    // 只有这一个单参数构造方法,访问权限同时也设为保护级,则子类必须显式调用此构造方法
    protected ProxyFactory(Object target) {
        super();
        this.target = target;
    }
 
    /**
     * 动态生成代理对象
     *
     * @return 代理对象
     */
    // 方法设为final,防止子类重写此方法
    public final Object getProxyInstance() {
    /*
     * Proxy.newProxyInstance方法的三个参数依次是目标对象的类加载器loader、
     * 目标对象实现的接口数组interfaces、InvocationHandler对象handler。
     * InvocationHandler是个接口,而当前抽象类implements此接口,此处的抽象类并没有具体实现此接口,具体实现将交给子类
     */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
 
}

主要是实现思路,当我们去进行重写的时候,我们会大概率地去实现全部内容,其实这样的代码逻辑是不清晰的,就比如说这一块接口方法的具体实现交给抽象工厂类的子类去完成,这其实很容易去了解,当子类去继承父类的时候,是需要重写父类的方法的,如果我们在父类中就重写了接口方法,那代码量就比较大了,而子类又有些特征,就是可以有自己的特性,所以在书写代码时要尽量避免逻辑或代码重复,这样最好的实现就是各司其职。
定义子类


public static class PrimaryProxyFactory extends ProxyFactory {
    public PrimaryProxyFactory(Object target) {
        super(target);
    }
 
    /**
     * 实现InvocationHandler接口,增强目标类实现接口的方法
     */
    @Override
    public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("需要移动10公里");   //目标类方法执行前的修饰
        Object returnVal = method.invoke(this.target, args); //调用目标类的相应方法
        System.out.println("费时100分钟");//目标类方法执行后的修饰
        return returnVal;
    }
}
 

public static class SeniorProxyFactory extends ProxyFactory {
 
    public SeniorProxyFactory(Object target) {
        super(target);
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("需要移动100公里");
        Object returnVal = method.invoke(this.target, args);
        System.out.println("费时100分钟,因为长距离驾车移动");
        return returnVal;
    }
}

测试

public static void main(String[] args) {
    People human= new People("阿里巴巴");
    //创建代理工厂类
    ProxyFactory primaryProxyFactory = new PrimaryProxyFactory(human);
    //代理类向上转型为Human接口
    Animal proxyPrimaryHuman = (Animal) primaryProxyFactory.getProxyInstance();
    //代理类执行接口方法
    proxyPrimaryHuman.move();
 
    System.out.println("");
    ProxyFactory seniorProxyFactory = new SeniorProxyFactory(human);
    Animal proxySeniorHuman = (Animal) seniorProxyFactory.getProxyInstance();
    proxySeniorHuman.move();
}

最终的接口方法还是由子类实现,我们在代理转型的时候调用了目标类从而实现目标类的方法。这就是整体的一个逻辑,我们不直接操作目标类,而是创建代理类去实现目标类。


总结

通过上面的JDK动态代理和之前的适配器模式就可以看出,其实最根本的区别在于动态代理生成了代理类去操作目标类,而适配器模式是实现适配器将适配者和目标接口绑定在一起。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值