LEEDOM

Jan 09, 2021

设计模式:结构型设计模式--代理模式

通过一个简单的例子来表达的这几个类的关系:我需要告诉女朋友今晚买了票一起看电影,这个时候她在房间里,我没法进去就没法直接通知到她,而她爸爸正好在门口看书,如果关系和我还不错,在听到我说让我通知女朋友今晚看电影时,就朝屋子里的女朋友喊今晚我要和她看电影,这个时候女朋友也就听到了。在这个场景中,父亲和女儿是需要在同一个模块下才能通信的,并且父亲需要能通过喊话通知到女儿的能力(也就是必须持有Daughter类的实例),否则要是父亲是哑巴,那整个调用过程其实是不可行的。

代理模式分为动态代理静态代理,这里的动静指的是是否是在运行时就已经确定了代理对象,一般情况下,静态代理由程序员直接编码就实现了,而动态代理则是程序在运行过程中才知道代理对象是谁。下面通过两个实例来解释。

静态代理的可以从AIDL中的应用去理解,首先AIDL是一种接口定义语言,作用是协助开发者自动生成Binder`通信需要的辅助类。

以下是一个AIDL使用过程中所涉及的几个核心的类的伪代码:

aidl_gen_code

接下里讲讲辅助类的内容:

  • 一个是AIDL中定义的接口的实现基类Base,这个Base继承自Binder并实现了AIDL的接口(假设接口是aidl),它对外的身份就有两个:Binderaidl。对客户端来说,能拿到的也是这两个身份,由于aidl是客户自己所设计的接口,不具备通用性,在框架设计上,传递的都是Binder对象。在使用时服务端就会把继承自Base的具体实现类BaseImpl的实例以Binder对象的身份返回给客户端,客户端拿到后,因为是个Binder对象,没法直接使用aidl`中定义的方法。
  • 另一个Proxy类的作用就出现,在调用asInterface的方法中,实际会生成一个Proxy对象返回给客户端,Proxy根据上面的UML可以知道对应的是Father的角色,而服务端对应的就是Daughter的角色,他们都实现了aidl接口,并且Proxy中持有了Binder对象(实际就是服务端返给客户端的BaseImpl的实例)。此时,客户端调用ProxyProxy调用BaseImpl这样一个完整的通信链条就连接好了。

可能到这里,会觉得好像是把复杂的事情搞麻烦了,一开始我拿到baseImpl来个转换不就好了,对应简单场景这样用肯定没有问题,但设计模式就是用来解决通用性问题的。例如这次我是喊女朋友看电影,下次可能就是我在屋外放烟花,由于之前定义的行为中只有听的操作,你总不能让女朋友只听烟花的声音吧,那还不如拿个音响放放,这样你又需要定义一个看的行为的接口。所以为了解决这一类的问题,就有了AIDL这种设计,而它这里应用的就是静态代理模式,不让我们直接访问到Binder对象的细节,而是把一切都通过接口定义来实现,这就好比,如果每一次看电影,都需要我买好票,然后通知女朋友票买好,还要为她规划好出门的理由或者路线,那就太累了,要是我只负责买票和通知,然后只需要在影院门口等就轻松很多了,对于女朋友是找借口出门还是翻窗户出门我一点都不关心(现实中还是前者好点,不然会被打的)分清两个人的职责。对应面向接口编程,细节实现不应该是调用者关心的。

动态代理的应用在Retrofit中可谓是表现得淋漓尽致。想想刚刚静态代理的场景中,代理类是很核心的一个类,它完成了Binderaidl两个类的动作转换,同时隐藏了Binder类(这种感觉突然有点像地下工作者哈哈,在外面的身份是特务,但是在交通站接收任务的时候又是友方人员,执行任务)。不过这个Proxy是先生成好的,我们在代码中才能调用,这样有个问题是它是固定的,也就是作为交通站,我知道我要代理谁,那万一出了叉子敌人不是把我们一锅端了,所以后来就改进了这个问题,我们不知道具体谁是那个卧底(代理对象),只知道他有个一个身份,叫胡峰同志,这样就不需要管具体接头做任务的是谁,只需要确定他是胡峰就可以了。而动态代理也差不多是这样的一个工作模式。动态代理伪代码如下:

dyn_proxy

在代码执行过程中,代理会通过实现接口,然后反射的形式来获取方法,最后调用InvocationHandler对象的invoke方法,我们只需要在这个invoke方法里实现具体的逻辑就行了。

接着看看Retrofit中的实现:

retrofit_dyn_proxy

这个方法是创建Retrofitcreate方法,传入的参数是我们自定义的网络接口。可以看到这个方法中返回的代理类是动态生成的,在我们具体调用service的某个方法时,代理类中会去执行InvocationHandlerinvoke,来具体执行网络的请求,就这样我们仅需要做好定义网络接口这一件事,其他的工作都被自动执行了,不再需要维护各种各样的代理类、封装类,不仅仅是极大的简化了代码,更重要的是这种操作能规避很多危险,开发者能做的事情越少,对一个成熟的系统而言就是越安全稳定

OLDER > < NEWER