结构型设计模式之代理模式

1 代理模式概念

1.1 介绍

  代理模式(Proxy Pattern)是面向对象中一种非常常见的设计模式。其实,不单是在软件开发领域,在我们的日常生活中对于代理也时常可见。比如:房东要将自家的房租出售,于是到房地产中介公司找一个代理,由他来帮自己完成销售房屋,签订合同等等事宜。在此,就以该生活场景为蓝本介绍Java的代理模式。一般而言,代理技术可分为:静态代理和动态代理;我们先来看静态代理。

1.2 定义

  代理模式也称委托模式,是结构型设计模式之一。是应用广泛的模式之一。

1.3 使用场景

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

2 UML类图

这里写图片描述
图片来源于:设计模式之五 — 代理(Proxy)模式

3 静态代理示例

(1)房东通过一纸协议将自己的房子挂靠在房屋中介公司,委托中介出售其房屋。嗯哼,我们来一起瞅瞅这个房东和中介公司共同达成的协议:

public interface TradeProtocol {
    public void sellHouse(int money);
}

(2)这个协议很简单,房东委托中介公司售卖自己的房子。既然是房东和房屋中介共同达成的协议,那么房东和中介都需要遵守该协议。先来看房东:

public class Owner implements TradeProtocol{
    @Override
    public void sellHouse(int money) {
        System.out.println("我是房主,我的房子卖了"+money+"块钱");
    }
}

(3)再来看中介:

public class HouseAgent implements TradeProtocol {
    private TradeProtocol mHouseProtocol;
    public HouseAgent(TradeProtocol houseProtocol){
        mHouseProtocol=houseProtocol;
    }
    @Override
    public void sellHouse(int money) {
        System.out.println("我是中介,业主的房子卖了"+money+"块钱");
        mHouseProtocol.sellHouse(money);
    }
}

  请注意HouseAgent的实现

  • 在HouseAgent的构造方法中传入TradeProtocol的对象,比如Owner
  • 在HouseAgent的sellHouse()中调用TradeProtocol的对象(比如Owner)的sellHouse()
  • 所以,HouseAgent售卖(sellHouse())房子,实际上是房东自己售卖了房屋!
    (4)测试如下:
public class Test {
    public static void main(String[] args) {
        Owner owner=new Owner();
        HouseAgent houseAgent=new HouseAgent(owner);
        houseAgent.sellHouse(10000*100);
    }
}

(5)测试结果:

我是中介,业主的房子卖了1000000块钱
我是房主, 我的房子卖了 1000000块钱

4 动态代理示例

  在该静态代理中,房东将房屋的售卖交给了中介代理,中介将房屋出售后又把钱一分不少地交给了房东。中介是怎么赚钱的呢?
(1)与之前一样,房东和房屋中介公司之间存有一纸协议:

public interface TradeProtocol {
    public void sellHouse(int money);
}

(2)同样地,房东遵守该协议:

public class Owner implements TradeProtocol{
    @Override
    public void sellHouse(int money) {
        System.out.println("我是房主,中介告诉我:房子卖了"+money+"块钱");
    }
}

(3)现在,房东又去房屋中介公司挂靠自己的房子,到了那一看:原来熟悉的那个中介出去办事了,刚好不在。正准备走呢,中介公司的经理笑嘻嘻地说:请问您是来办理业务的吗?来,请坐,我给你们引荐以为我们这里服务最好的一个房屋中介人员办理您的相关事宜!看看这个这位被称为”服务最好的中介”是怎么卖掉房东的房子的:

public class TestProxy02 {
    public static void main(String[] args) {
        TradeProtocol owner=new Owner();
        //获取被代理类中介的ClassLoader
        ClassLoader classLoader=owner.getClass().getClassLoader();
        Class<?>[] interfaces = owner.getClass().getInterfaces();
        //创建InvocationHandler
        HouseAgentInvocationHandler invocationHandler=new HouseAgentInvocationHandler(owner);
        //动态构造一个代理者中介
        TradeProtocol tradeProtocol=(TradeProtocol) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        //中介买房子
        tradeProtocol.sellHouse(1000000);
    }
}

(4)HouseAgentInvocationHandler 的实现如下:

public class HouseAgentInvocationHandler implements InvocationHandler {
    private Object object;
    public HouseAgentInvocationHandler(Object object) {
        this.object=object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Integer money=(Integer) args[0];
        System.out.println("我是中介,业主的房子我实际卖了"+money+"块钱");
        //执行被代理对象方法
        Object result=method.invoke(object, new Object[]{money/2});

        System.out.println("我是中介,这次交易我赚了不少钱!!!");
        return result;
    }
}

(5)控制台输出信息如下

我是中介,业主的房子我实际卖了1000000块钱
我是房主,我的房子卖了500000块钱
我是中介,这次交易我赚了不少钱!!!

(6)分析:中介把房子卖了100W却告诉房东只买了50W,自己狠狠地赚了一笔!这是怎么回事呢?不急,我们通过这两个示例来回顾一下卖房子的经过。

  • 第一个示例中,房东将房屋委托给一个自己熟知的中介人员售卖房产;中介卖了多少钱就给房东多少钱;
  • 第二个示例中,房屋中介公司的经理临场(动态)地指定了一个中介人员为房东办理售卖房屋的相关事宜;
  • 第二个示例中,中介对售卖的过程做了手脚。由此可见:动态代理可方便地对被代理类的方法进行某些处理。比如,在方法实际调用前做一些过滤或者拦截操作或者修改,在方法调用后做一些善后处理等。

5 动态代理技术分析

  在Java的动态代理机制中,有两个非常重要的不可或缺的东西: InvocationHandler接口和Proxy类。

5.1 先看InvocationHandler接口:

  每个动态代理类都必须要实现InvocationHandler接口,并且每个动态代理类的实例都关联到了一个handler。当我们通过代理调用被代理对象的方法时,该方法的调用会被转发至InvocationHandler接口的 invoke( )中进行处理,方法如下:

/**
 * proxy:最终生成的代理
 * method:被代理对象正在执行的方法
 * args:被代理对象正在执行的方法的输入参数
 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{}
/**
 * 在HouseAgentInvocationHandler类的invoke( )方法开头加几句打印语句:
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy.getClass()="+proxy.getClass());
        System.out.println("method.getName()="+method.getName());
        for(Object object:args) {
            System.out.println("object.getClass()="+object.getClass());
        }
        ..............
}
/**
 * 输出结果
 */
proxy.getClass()=class com.sun.proxy.$Proxy0
method.getName()=sellHouse
object.getClass()=class java.lang.Integer

5.2 再来看Proxy类

  Proxy用于动态创建一个代理类及其实例。该类中我们常用 newProxyInstance( )方法:

/**
 * loader:加载动态代理类的ClassLoader,该输入参数通常为被代理类的类加载器
 * interfaces:动态代理类需要实现的接口,该输入参数通常为被代理类使用的接口
 * handler:动态代理对象在调用方法时与之关联的InvocationHandler
 */
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler handler)  throws IllegalArgumentException {}

5.3 小结

(1)动态代理与静态代理的原理是一样的,但是在动态代理中它没有具体的代理类而是通过Proxy在JVM运行时利用反射动态地生成了一个代理。在静态代理中,需要代理(中介)与被代理对象(房东)遵守同一份协议;动态代理对于协议的遵守发生在newProxyInstance( )时;并且动态代理可以对原来协议中的方法”做手脚”!

(2)梳理一下动态代理示例及其相关技术

  • 房东遵守房屋售卖协议动态地生成代理,在生成代理的过程中代理也遵守了房屋售卖协议;
  • 代理在执行协议中的方法时(比如sellHouse( )),将该方法转发至与该代理密切相关的InvocationHandler中;
  • 在InvocationHandler中可对协议中的方法(sellHouse( )),还进行额外的附加的处理;
  • 在InvocationHandler中利用反射调用协议中的方法(sellHouse( ))。其实,动态代理属于Java反射技术范畴,从导包语句中也有所发现:java.lang.reflect.InvocationHandler;

6 Android源码中的代理模式实现

(1)ActivityManagerProxy代理类
ActivityManager是Android中管理和维护Activity的相关信息的类,为了隔离它与ActivityManagerService,有效降低二者的耦合,在这中间使用了ActivityManagerProxy代理类,所有对ActivityManagerService的访问都转换成对代理类的访问,这样ActivityManager就与ActivityManagerService解耦了。

7 总结

7.1 优点

(1)对代理者与被代理者进行解耦。
(2)代理对象在客户端和目标对象之间起到一个中介的作用,这样可以起到对目标对象的保护。

7.2 缺点

基本没有缺点,真要说缺点就是设计模式的通病:对类的增加。

8 参考链接

转载来于:从狡猾的房产中介来看动态代理
《Android源码设计模式解析与实战》读书笔记(十八)

猜你喜欢

转载自blog.csdn.net/chenliguan/article/details/78807311