Java 设计模式之代理模式

设计模式系列

创建型设计模式

Java 设计模式之单例模式

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

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

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

Java 设计模式之Builder模式

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

结构型设计模式

Java 设计模式之代理模式

Java 设计模式之外观模式

前言

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

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

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

什么是代理模式

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

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

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

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

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

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

UML类图

UML类图

图 1    代理模式的 UML 类图

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

示例

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

UML类图

示例的 UML 类图

图 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
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值