设计模式之代理模式

引言

设计模式在程序员在开发过程中经常会用到的一些写代码的套路,有的人可能会说我在写代码的时候怎么没用过呢,其实很可能你已经用过了,只是你不知道它的学名原来叫xxx模式,可能你的这种套路习惯是学习原来公司老的代码写的,或是同事对你的指点分享,或是学网上写的…

Proxy_pattern_diagram.png

这个图片是代理模式的UML图,引自wiki。

从这个UML图中,我们可以看到,代理模式中主要有三个角色,分别是Subject(目标)RealSubject(真正实现)Proxy(代理)。我们怎么理解这三个角色,以及它们之间的关系呢。举个例子。可以想象有这样一个场景:马上就到你女朋友的生日了,该准备什么礼物呢,旁敲侧击了解到原来她想要神仙水。(PS:心疼ing…)

然后自己网上比较了一下价格,发现还是找代购从国外带比较便宜,于是就找了做代购的朋友小A帮我买神仙水。

买好之后,让小A把神仙水寄到了我女朋友的公司,在生日当天,女朋友收到了神仙水,(@ο@) 哇~,好开心,谢谢小A,你既然还记得我的生日,我男票都不记得哎!

纳尼.jpeg

纳尼!!!!!这是”我”花钱给你买的好吧!!!

上面这个场景中,Subject是买神仙水,是我们的目标(需求),真正的买家是你(RealSubject),代购小A是你的代理(Proxy)。在你女朋友看起来是代购小A买的神仙水,但是实际上是你画的钱买的。

除此之外生活中还有许多代理模式的场景,比如:找律师打官司,律师是你的代理,你只需要做你该做的,其余的事情律师为你处理;明星的经纪人是明星的代理,商演,排档期等合作找经纪人,真正表演的是明星。。。

定义

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

代理模式优缺点

优点:职责清晰,真实的角色(RealSubject)就是实现实际业务逻辑,不用关心其他的,对应的Proxy代理,是锦上添花,可做扩展。

面向接口编程,扩展型比较高,业务变化时,场景类无需改变。

缺点:

静态代理下,如果业务逻辑变了(Subject 内的方法变了),虽说场景类不需要改变,但是对应的RealSubject,Proxy需要改动,因为这两个是实现了Subject的。

代理模式的使用场景

当无法或者不想直接访问某个对象或访问某个对象存在困难时,可以通过一个代理对象来间接访问。

Java中的代理模式可分为静态代理和动态代理,我们一个个的看。

静态代理

静态代理是指静态的定义代理类,就是我们自己手写定义代理类的代理方式。

拿上面的买神仙水的例子来说,我们用代码的方式来表示这个场景。

首先核心目标(业务)是买神仙水,因此我们定义一个目标接口,也可以是抽象类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 /**

 \* 抽象目标(业务),可以是interface接口或者Abstract类

 */

interface Subject {

   void buyGodWater();

}

然后分别定义RealBuyer(真实买家),Proxy(RealBuyer的代理),注意RealBuyer(真实)和Proxy(代理)都必须要实现Subject目标接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/**

	/* 真正做业务的,实现Subject

 */

class RealBuyer implements Subject {

    

    @Override
    public void buyGodWater() {
        System.out.println("RealBuyer buy God Water");
    }

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**

 \* RealBuyer的代理,实现Subject

 */

class ProxyBuyer implements Subject {

    private Subject mSubject;



    //通过构造方法传入RealBuyer
    public ProxyBuyer(Subject subject) {

        this.mSubject = subject;

    }



    @Override
    public void buyGodWater() {

        onBuyBefore();

        mSubject.buyGodWater();

        onBuyAfter();

    }





    /**

     \* 可选方法,对业务逻辑进行填补,修饰,完善等

     */
    private void onBuyBefore(){



    }



    /**

     \* 可选方法,对业务逻辑进行填补,修饰,完善等

     */
    private void onBuyAfter(){



    }

}

我们在场景中通过Proxy购买神仙水,表面上是ProxyBuyer在购买神仙水,但实际上真正付钱购买的是RealBuyer

1
2
3
4
5
6
7
public static void main(String args[]){

    ProxyBuyer buyer = new ProxyBuyer(new RealBuyer());

    buyer.buyGodWater();

}
1
2
3
RealBuyer buy God Water

Process finished with exit code 0

通过输出我们看到是RealBuyer在购买神仙水。

动态代理

动态代理中,代理类不是我们自己定义的,是利用JDK的API ,在程序运行阶段通过反射机制系统动态生成的。

它利用JDK中Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法和InvocationHandler接口来实现动态代理。

Proxy.newProxyInstance方法中,第一个参数loader是代理类的类生成器;第二个参数是需要代理类实现的接口集合;最后一个参数就是InvocationHandler接口了,这个接口是一个函数式接口,它只有一个抽象方法invoke,每个代理实例都对应一个InvocationHandler接口,当通过代理调用业务逻辑(方法)时,接口中对应的invoke方法就会被调用。

动态代理的实现是有套路的,接下来我们用动态代理的方式实现上面的买神仙水场景。

首先,抽象接口Subject代码和RealBuyer的代码不用变化。创建一个实现InvocationHandler接口的类DynamicProxyHandler。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class DynamicProxyHandler implements InvocationHandler {

        //被代理的对象
        private Subject mSubject;

        public DynamicProxyHandler(Subject subject) {
            this.mSubject = subject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("invoke: Method name = " + method.getName());
            //执行被代理的方法,可在此语句前后按需求加上补充增强逻辑
          	Object p = method.invoke(mSubject, args);
            return p;

        }
    }

然后看场景类逻辑。

1
2
3
4
5
6
public static void main(String args[]) {
        RealBuyer realBuyer = new RealBuyer();
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(realBuyer);
        Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, dynamicProxyHandler);
        subject.buyGodWater();
    }

输出结果如下:

1
2
3
4
invoke: Method name = buyGodWater
RealBuyer buy God Water 

Process finished with exit code 0

在InvocationHandler接口中的invoke方法中,可以在method.invoke执行被代理的方法前后,做一些补充增强逻辑。通过上面的代码,我们看到使用动态代理,没有创建代理类,所以上面提到的改变Subject中抽象方法(业务逻辑)时,就只需要改RealSubject的逻辑即可,代理类就不用管了。 上面也说了java的动态代理是有套路的,基本上都是一个流程,都需要抽象目标类Subject,被代理的RealSubject,还有JDK中的那两个API。

动态代理UML

分类

上面说代理模式分为静态代理和动态代理,是以代码层次为参照分类的;也可以从其适用范围来分类可分为:

  • 远程代理(Remote Proxy)
  • 虚拟代理(Virtual Proxy)
  • 保护代理(Protection Proxy)
  • 缓存代理(Cache Proxy)
  • 智能引用(Smart Reference)

上面列的一些分类,有的可能没听说过,看着可能会比较慌,是不是都要记,别慌,这些代理只不过是代理模式的思想在不同业务场景下的应用罢了,用久了为了方便就给起了个名字,核心的部分是要理解代理模式思想,这样你在开发中根据自己的业务逻辑可能还会创建出属于自己的新的代理。

代理模式的应用

可能在平常自己的开发工作中会很少的使用代理模式,但是在很多开源库,android系统源码中有很多地方用到了代理模式。比如:

square的Retrofit库它在创建Service时,其create(final Class service)方法的内部逻辑就用到了代理模式中的动态代理。

Android源码中的ActivityManageProxy代理类,代理的是ActivityManagerNative的字类ActivityManageService。

Java Spring AOP编程。

参考

https://zh.wikipedia.org/zh-hans/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F

https://www.cnblogs.com/cenyu/p/6289209.html

Android源码设计模式与实战

码农翻身

设计模式之禅


个人公众号