这篇文章是设计模式系列文章的第七篇:代理模式
设计模式系列前几篇没看的可以点击对应的文章快速查看
设计模式(一):单例模式
设计模式(二):工厂模式
设计模式(三):生成器模式
设计模式(四):原型模式
设计模式(五):适配器模式
设计模式(六):装饰器模式
设计模式(七):桥接模式
我们还是老规矩,用一个具体案例开始我们的设计模式之旅
假如我们程序里具备了支付功能,有一个支付接口 Payment
public interface Payment { void doPay(); }
并且有一个支付宝实现类 AliPayment
public class AliPayment implements Payment{ @Override public void doPay() { System.out.println("支付宝支付"); } }
接下来我们要在支付宝支付之前输出请求参数和响应参数
可能我们最先想到的是直接修改 doPay() 方法就行了
但是,事实上很多情况下我们是无法直接修改的,比如我们没有这个类的源码
那我们最先想到的可能使用继承的方式来完成
新写一个 LogPayment类继承 AliPayment 这个类,然后重写 doPay() 方法
public class LogPayment extends AliPayment { @Override public void doPay() { System.out.println("输出请求参数"); super.doPay(); System.out.println("输出响应参数"); } }
这样就完成了输出请求参数和响应参数的功能(当然也可以使用 装饰器模式 来完成)
接下来我们再继续增加功能,在输出请求参数之前执行保存数据库参数,在输出响应参数之后执行更新数据库操作
我们继续使用继承的方式来完成,新增 DbPayment 来继承 LogPayment 并重写 doPay() 方法
public class DbPayment extends LogPayment { @Override public void doPay() { System.out.println("保存数据"); super.doPay(); System.out.println("更新数据"); } }
这样就满足了我们的要求。但是,细想一下还有两个问题
输出日志和保存数据操作本来不存在父子关系,我们只是为了实现功能而把他们强制进行了继承
调用者在使用支付功能时,有的调用者希望先保存数据再输出日志;有的调用者希望先输出日志再保存数据。这种继承实现的方式很难同时满足所有的调用者
我们可以尝试使用代理模式来解决这些问题
新建日志操作代理类 LogPaymentProxy 去实现 Payment 接口并重写 doPay() 方法
public class LogPaymentProxy implements Payment { private Payment payment; public LogPaymentProxy(Payment payment) { this.payment = payment; } @Override public void doPay() { System.out.println("输出请求参数"); payment.doPay(); System.out.println("输出响应参数"); } }
新建数据操作代理类 DbPaymentProxy,一样去实现 Payment 接口并重写 doPay() 方法
public class DbPaymentProxy implements Payment { private Payment payment; public DbPaymentProxy(Payment payment) { this.payment = payment; } @Override public void doPay() { System.out.println("保存数据"); payment.doPay(); System.out.println("更新数据"); } }
我们分别用两个代理类去实现了需求,这就是最简单的两个代理模式
在调用者希望先输出日志再保存数据时可以这样调用
Payment payment = new AliPayment(); DbPaymentProxy dbPaymentProxy = new DbPaymentProxy(payment); LogPaymentProxy logPaymentProxy = new LogPaymentProxy(dbPaymentProxy); logPaymentProxy.doPay();
在调用者希望先保存数据再输出日志时可以这样调用
Payment payment = new AliPayment(); LogPaymentProxy logPaymentProxy = new LogPaymentProxy(payment); DbPaymentProxy dbPaymentProxy = new DbPaymentProxy(logPaymentProxy); dbPaymentProxy.doPay();
代理模式是最常用的设计模式之一,在创建型模式、结构型模式和行为型模式分类中,代理模式归属于结构型模式
代理模式是对业务系统要访问的原对象提供一个代理对象,代理对象可以将请求转发给原对象,并且可以在请求的前后添加一些额外业务逻辑处理
代理模式的结构如下图

img
代理模式的实现方式主要分为三步
新建一个代理类实现接口类,并把接口类作为代理类的成员变量
在代理类中提供一个带参数的构造器,并对成员变量进行初始化
重写接口类的方法,添加自定义业务逻辑,并通过成员变量调用父类的方法
代理模式还分为静态代理和动态代理,本文例子使用的是静态代理
在设计模式系列完结之前,我会单独开一篇文章讲动态代理
为了避免本篇文章篇幅过长,也为了降低大家的学习成本,本篇文章不对动态代理做过多描述
代理模式的最大优点在于可以在调用原对象的前后,添加自定义的业务逻辑
降低耦合度,代理模式把原对象和调用者解耦, 使原对象更加专注自己本身的业务逻辑,非自身的逻辑可以交给代理对象处理
即使原对象还未准备好或不存在,也不影响代理对象的使用。代理对象可以在代理时再对原对象进行初始化
增加了代理类,方法调用链路变长,会增加响应时间
代码结构会变得相对复杂,增加理解成本
需要在原有功能的前后添加自定义业务逻辑时,可以考虑使用代理模式
在需要对已有功能增加业务逻辑,而又无法拿到源码时可以考虑使用代理模式
在需要对一个很重的对象进行生命周期管理时,可以使用代理模式,比如数据库对象、Spring容器对象
与装饰器模式对比
代理模式和装饰器模式的结构很相似,甚至代码实现上都一致,都是将一个对象的部分工作委托给另一个对象
不同的是代理模式通常是自己管理原对象的生命周期,装饰器模式的原对象的生命周期是交给调用者来管理的
还有就是他们的目的不一样,装饰器模式目的是为了增强原对象的行为,代理模式的目的是管理原对象的行为
比如把一个孩子作为原对象,孩子需要增加一个吃饭的行为
家长喂孩子吃饭,家长就是代理对象。家长还会在孩子吃饭之前给他系上围兜,吃饭之后给他擦嘴,这是代理模式
孩子自己学习吃饭,学会了之后的这个孩子就变成了另一个对象,这是装饰器模式
装饰器模式回顾:设计模式(六):装饰器模式
与适配器模式对比
适配器模式是对原对象进行封装,对外提供不同的接口,从而减少不同的调用者的修改
代理模式是对原对象进行封装,对调用者提供相同的接口
适配器模式回顾:设计模式(五):适配器模式
设计模式不是万能的,只有合理利用设计模式才能写出合理的代码