Java 设计模式之代理模式

设计模式系列

创建型设计模式

Java 设计模式之单例模式

Java 设计模式之静态工厂方法模式

Java 设计模式之工厂方法模式

Java 设计模式之抽象工厂模式

Java 设计模式之Builder模式

Java 设计模式之静态工厂、工厂方法、抽象工厂和 Builder 模式的区别

结构型设计模式

Java 设计模式之代理模式

Java 设计模式之外观模式

文章目录

    • 设计模式系列
      • 创建型设计模式
      • 结构型设计模式
    • 前言
    • 什么是代理模式
    • UML类图
    • 示例
      • UML类图
      • 静态代理
      • 动态代理
    • 总结
    • 感谢

前言

前面几篇文章,我们一直在聊 创建型设计模式,从本讲开始,我们来聊一聊 结构型设计模式。在 GOF 提出的 23 种设计模式中,结构型设计模式占有 7 席,分别为:代理模式、装饰模式、外观模式、享元模式、适配器模式、桥接模式、组合模式。

本讲的主题是 代理模式。代理模式,又被称为 “委托模式”,是结构型设计模式的重点,有些结构型设计模式,甚至可以认为是从代理模式优化而来的。

代理模式无论是在我们的程序中,还是在我们的生活中,都比较常见。比如打官司找的代理律师、买房租房找到的房屋代理、上网用的代理上网、室友同事帮我们买饭可以认为是买饭代理等。

什么是代理模式

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

使用场景:当无法或不想直接访问某个对象或访问某个对象存在困难时,可以通过一个代理对象来间接访问。为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。

通俗点来说,就是我不想干,或者干不了,找个代理让他干。比如说,打官司辩护我干不了啊,让律师干;租房子我不想找房东一个个问房子啥情况,让租房代理干;我懒我不想下去买饭,让同事带。

另外还有一点很重要,代理模式可以做方法增强

比如说,租房代理帮我筛选房子情况,他筛选一套房子发现太旧又太贵,直接pass不用叫我去看房了。我只管看房,代理负责筛选,这就是 “看房方法” 的增强。

当然,一个代理对象可以代理多个委托对象。 这个也可以与方法增强相结合:房子不符合我的要求,但可能符合另一个租房人的要求,那就让他去看房吧。

UML类图

Java 设计模式之代理模式_第1张图片

图 1    代理模式的 UML 类图

上文使用场景中有这么一句话:“为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口”。很好理解,既然是代理,就是代理对象替委托对象做事情,所以代理对象拥有与委托对象相同的行为,这些共同的行为就是由上面类图中的抽象类或者接口 Subject 规范的。

示例

下面以租房子的租房代理为例实践下代理模式,我们先来看看示例的 UML 图。

UML类图

Java 设计模式之代理模式_第2张图片

图 2    示例的 UML 类图

静态代理

1、House 类就是房子的实体类,提供一些 get 、set 方法。

程序清单 1     房子 Bean 类 House

public class House{

    // 房屋名
    private String name;
    // 价格
    private float price;
    // 面积
    private float area;

    public String getName(){
        return this.name;
    }

    public House setName(String name){
        this.name = name;
        return this;
    }

    public float getPrice(){
        return this.price;
    }

    public House setPrice(float price){
        this.price = price;
        return this;
    }

    public House setArea(float area){
        this.area = area;
        return this;
    }

    public float getArea(){
        return this.area;
    }

}

2、房屋租赁接口 IRent 规定了租房过程中的行为,由委托人和代理人进行实现。

程序清单 2     房屋租赁接口 IRent

public interface IRent{

    // 看房子
    public boolean lookHouse(House house);

    // 签合同
    public void signContract();

}

3、代理类 RentalProxy 是租房代理人,负责对租房方法进行增强,当符合要求时,才会通知租房委托人 MrA 执行实际的租房行为。

程序清单 3     租房代理类 RentalProxy

/**
 * 静态代理 —— 租赁代理
 */
public class RentalProxy implements IRent {

    private IRent mRent;

    public RentalProxy(IRent mRent) {
        this.mRent = mRent;
    }

    @Override
    public boolean lookHouse(House house) {

        if (house.getArea() < 50) {
            System.out.println("太小了,不符合小A的要求,不用通知小A去看房" + house.getName() + "了");
            return false;
        }

        // 执行代理对象的真实逻辑
        return mRent.lookHouse(house);
    }

    @Override
    public void signContract() {
        mRent.signContract();
    }

}

可以看到,代理人的构造方法中注入了租房接口,实际上注入的是租房委托人对象。

另外,租房代理人预先对房屋面积进行了筛选,只有房屋面积不小于 50 平米,才会通知租房委托人小 A 去看房。


4、MrA 是租房委托人,其执行实际的租房行为。

程序清单 4     租房委托人 MrA

public class MrA implements IRent {

    @Override
    public boolean lookHouse(House house) {
        // 代理对象执行实际的业务
        System.out.println("小A去看了房子" + house.getName());
        return true;
    }

    @Override
    public void signContract() {
        // 代理对象执行实际的业务
        System.out.println("小A签了租赁合同");
    }

}

5、main 函数作为调用方,调用了上述代码。

程序清单 5     静态代理调用方 main 主函数

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

public class Main {

    public static void main(String[] args) {

        House houseM = new House();
        houseM.setName("M").setArea(40F).setPrice(2500F);

        House houseN = new House();
        houseN.setName("N").setArea(60F).setPrice(4000F);

        /*
         * 静态代理
         */
        // 代理对象
        MrA mrA = new MrA();
        // 委托对象
        RentalProxy rentalProxy = new RentalProxy(mrA);
        if(rentalProxy.lookHouse(houseM)){
            rentalProxy.signContract();
        };
        if(rentalProxy.lookHouse(houseN)){
            rentalProxy.signContract();
        };
        
    }

}

执行结果如下:
执行结果
可以看到,房子 M 面积不符合要求,代理人不会通知委托人看房子及签订合同;房子 N 符合要求,会通知委托人看房子及签订合同。

我们已经知道,上面这种是静态代理的实现方式。实际上静态代理与动态代理的区分,正是从编码的角度来说的(还有从适用范围的角度来区分的代理类型,诸如远程代理、虚拟代理、安全代理等,本文不做延伸了)。

说一下 静态代理和动态代理的分别

静态代理,在代码运行前,就已经存在了代理类的 .class 编译文件;

动态代理,在代码运行时,通知反射来动态地生成代理类的对象,并确定到底来代理谁。也就是我们在编码阶段无须知道代理谁,代理谁将会在代码运行时动态决定。

Java 提供了动态代理的接口 InvocationHandler,实现该接口需要重写 invoke() 方法。

下面我们还是从代码上来理解动态代理模式。

动态代理

动态代理与静态代理在代码上的区分主要是 代理类,我们看下生成动态代理的类 DynamicPurchasing。其需要实现接口 InvocationHandler 并实现接口中的方法 invoke()

程序清单 6     动态代理的生成类 DynamicPurchasing

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

/**
 * 动态代理的生成类
 */
public class DynamicPurchasing implements InvocationHandler{

    private Object object;

    public DynamicPurchasing(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 反射得到代理对象
        Object result =  method.invoke(object, args);
        return result;
    }

}

在上述代码中,我们声明了一个 Object 的引用,该引用指向委托类,之后通过 invoke() 方法反射得到动态代理对象。

那这个类,我们该怎么用呢?我们来看一下调用方代码。

程序清单 7     调用方 main 主函数

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

public class Main {

    public static void main(String[] args) {

        House houseM = new House();
        houseM.setName("M").setArea(40F).setPrice(2500F);

        House houseN = new House();
        houseN.setName("N").setArea(60F).setPrice(4000F);

        /*
         * 动态代理
         */
        InvocationHandler invocationHandler = new DynamicPurchasing(mrA);
        IRent rent = (IRent) getProxyInstance(mrA.getClass(), invocationHandler);
        if (rent != null) {
            rent.lookHouse(houseM);
            rent.lookHouse(houseN);
        }

    }

    // 生成动态代理
    private static Object getProxyInstance(Class clz, InvocationHandler handler) {
        return Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), handler);
    }

}

上述代码第 17 行,调用 DynamicPurchasing 构造时传入了委托人 MrA 的对象,即我们上面提到的指向委托人的 Object 引用,也就是说,我们要生成 MrA 的代理对象;

上述代码第 18 行,调用 getProxyInstance() 方法生成了代理对象。

getProxyInstance() 中调用的方法是 Java 的 Proxy 类提供的,

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

介绍一下参数:

  • ClassLoader loader,委托类的类加载器;
  • Class[] interfaces,委托类实现的接口集合,生成的代理类也要实现这些接口;
  • InvocationHandler h,即我们的动态代理的生成类 DynamicPurchasing

这样,我们就得到了一个动态代理对象,可以通过此对象调用委托人的实际行为了。

那如果要在动态代理中做方法增强,该怎么实现呢?

重点还是在 InvocationHandler 接口中的方法 invoke(),invoke() 的参数 method 即调用方要调用的委托对象的方法,参数 Object[] args 即此方法入参。我们改写下代码:

程序清单 8     改写 invoke() 方法实现方法增强

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 反射得到代理对象
        if ("lookHouse".equals(method.getName())) {
            House house = (House) args[0];
            if (house.getArea() < 50) {
                System.out.println("太小了,不符合小A的要求,不用通知小A去看房" + house.getName() + "了");
                return null;
            }
        }
        Object result = method.invoke(object, args);
        return result;
    }

总结

从上面的分析和例子我们可以看出,静态代理类是在编码阶段就确定了的,代理类持有委托类的引用;动态代理类是在代码的执行过程中动态生成的,更灵活,能够实现委托对象和代理对象的解耦。

另外,如果不需要做方法增强,动态代理的生成类 DynamicPurchasing 与业务无关,一个项目甚至可以只存在这么一个动态代理的生成类;当需要在动态代理对方法增强时,就如代码清单 9 所示,动态代理的生成类 DynamicPurchasing 与业务相关,项目中可能会存在多个 DynamicPurchasing

本讲的示例源码已经放在 Gihub 上:ProxyPattern

感谢

  • 《Android源码设计模式解析与实战》 何红辉 关爱民
  • 《Android进阶之光》 刘望舒
  • 在线绘图网站 ProcessOn

你可能感兴趣的:(架构设计)