代理模式之静态代理

首先引入故事背景:

小A想买一瓶法国的香水送给自己的女朋友,但是这种香水国内没有售卖点,而自己去法国则花费的时间和金钱成本都很高。这时小A突然想到自己的好朋友小B正好在法国游玩,于是小A托小B给他带一瓶香水,同时允诺5%的好处费。于是小B答应了,小B在法国买到了对应的香水,回国后交给了小A,而小A则完成了在中国购买法国香水。

在上述故事中,小A就是客户,小B就是我们常说的代理商,而香水厂家则是一个真实的销售对象。

再引入一个故事,小A毕业想租房,但是小A却找不到房东的联系方式,此时房屋中介小B找到了小A,于是小A带着去各处看房并成功帮小A租到了房子,在整个过程中,小A并未与房东谋面。

在上述租房的故事中可以看出,小A的需求是买房子,房东的需求是卖房子,但是由于房东不宣传,小A很难找到房东的联系方式。此时中介作为代理商,沟通了小A和房东,同时提供了一些额外的服务,例如看房等。

上述故事都是经典的代理模式的现实生活实现。

定义

代理模式的定义:

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

最基本的代理模式中有4个对象:

抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。例如前面背景故事中的卖酒和出租房屋。

真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。例如前面背景故事中的香水厂家和房东。

代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。例如前面背景故事中的小B和房屋中介。

客户(ProxyTest)类:需要被服务的对象。例如前面背景故事中的买香水的小A以及租房的小A。

四个对象的关系图如下所示:

在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。

根据代理的创建时期,代理模式分为静态代理和动态代理。

静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。

动态:在程序运行时,运用反射机制动态创建而成

代码实现

案例一:中介租房

针对前面中介租房的问题,对于房东来说,他只想把房子卖出去,不想去花时间做宣传。对于客户来说,他想找到房东来完成看房,租房的需求。但是由于房东不宣传,客户无法直接接触到房东。

此时中介应运而生,房东可以直接把信息告诉中介,告诉他我这里有空房子,然后中介来进行宣传,而客户可以通过中介来完成看房,租房的操作。

从上面的例子中看出,房东和中介都在完成一个任务:租房,这就是前面的抽象主题,将其抽离出来做成一个接口:

package com.zhu.proxy.staticProxy.demo01;public interface Rent {// 租房void rentHouse();}

而房东就是真实主题对象,他只需要完成租房的任务,其他的事情他完全可以不管。

package com.zhu.proxy.staticProxy.demo01;// 房东public class Host implements Rent{@Overridepublic void rentHouse() {System.out.println("房东成功租出去房子");}}

有了中介的好处在于,房东可以不需要额外的操作,他只需要专心致力于出租房屋即可,其余的事情一概不需要负责,例如看房、签合同等等。这些其他的事情都是由中介来代理完成。

需要注意的是,虽然中介也需要实现租房的接口,但是中介出租的是房东的房子,而不是自己的房子,故需要有一个房东属性,然后调用这个房东的租房方法。

package com.zhu.proxy.staticProxy.demo01;// 中介public class Proxy implements Rent {// 房东Host host = new Host();// 租房@Overridepublic void rentHouse() {seeHouse(); // 看房sign(); // 签合同host.rentHouse(); // 完成租房fair(); // 收取中介费}// 带客户看房public void seeHouse() {System.out.println("看房中。。。。。。");}// 签合同public void sign() {System.out.println("签订租赁合同。。。。。。");}// 收费public void fair() {System.out.println("收取中介费");}}

对于客户来说,他只需要找到一个中介即可完成租房的需求。

package com.zhu.proxy.staticProxy.demo01;// 客户端public class ProxyTest {public static void main(String[] args) {Proxy proxy = new Proxy();proxy.rentHouse();}}

执行结果如下所示

看房中。。。。。。签订租赁合同。。。。。。房东成功租出去房子收取中介费案例二:Service打印日志

上述案例为一个真实场景的模拟,其实感觉比较牵强,还是不太知道代理模式的强大。

接下来通过一个真实的业务场景来体现代理模式的作用。

首先假设有一个Service代码,里面包含数据库的增删查改操作。

package com.zhu.proxy.staticProxy.demo02;// 服务接口public interface Service {void add(); // 增void delete();// 删void query(); // 查void modify();// 改}

通过改接口实现一个具体的Service类:

package com.zhu.proxy.staticProxy.demo02;import javax.xml.soap.SAAJResult;public class ServiceImpl implements Service {@Overridepublic void add() {System.out.println("增加元素");}@Overridepublic void delete() {System.out.println("删除元素");}@Overridepublic void query() {System.out.println("查找元素");}@Overridepublic void modify() {System.out.println("修改元素");}}

这样就算是完成了一个完整的业务代码。

接下来需要在改业务代码上进行扩展,来了新的需求,要求打印日志,即增加元素的时候打印出“增加了一个元素”的提示信息。

根据需求可以很直接的想到,直接在ServiceImpl中增加一行代码即可。但是这样做在公司中是大忌,原本跑的很正常的业务代码是不能乱动的,如果因为你的修改导致原本可以跑的通的业务代码失效了,就会引起很大的麻烦,错误排查会十分麻烦。

正确的方法应该是使用代理模式去进行实现。借助前面中介的思想,这里ServiceImpl就是房东,他只需要负责业务代码,其他的不需要实现,而日志输出的任务交给代理去做就好。

package com.zhu.proxy.staticProxy.demo02;public class ServiceProxy implements Service{Service service = new ServiceImpl();@Overridepublic void add() {log("增加元素ing");service.add();}@Overridepublic void delete() {log("删除元素ing");service.delete();}@Overridepublic void query() {log("查找元素ing");service.query();}@Overridepublic void modify() {log("修改元素ing");service.modify();}// 打印日志public void log(String msg) {System.out.println("[DEBUG]: " + msg);}}

总结

静态代理的 优点:

代码结构简单,较容易实现

静态代理的 缺点: 

无法适配所有代理场景,如果有新的需求,需要修改代理类,不符合软件工程的开闭原则

静态代理的 使用场景:

当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。