大家好,欢迎来到编程队伍,我是作者王小伍,你可以叫我伍先生
这篇文章是设计模式系列文章的第七篇:代理模式
设计模式系列前几篇没看的可以点击对应的文章快速查看
正文
我们还是老规矩,用一个具体案例开始我们的设计模式之旅
假如我们程序里具备了支付功能,有一个支付接口 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();
基本介绍
代理模式是最常用的设计模式之一,在创建型模式、结构型模式和行为型模式分类中,代理模式归属于结构型模式
代理模式是对业务系统要访问的原对象提供一个代理对象,代理对象可以将请求转发给原对象,并且可以在请求的前后添加一些额外业务逻辑处理
代理模式的结构如下图
代理模式的实现方式主要分为三步
-
新建一个代理类实现接口类,并把接口类作为代理类的成员变量
-
在代理类中提供一个带参数的构造器,并对成员变量进行初始化
-
重写接口类的方法,添加自定义业务逻辑,并通过成员变量调用父类的方法
代理模式还分为静态代理和动态代理,本文例子使用的是静态代理
在设计模式系列完结之前,我会单独开一篇文章讲动态代理
为了避免本篇文章篇幅过长,也为了降低大家的学习成本,本篇文章不对动态代理做过多描述
优缺点
优点
代理模式的最大优点在于可以在调用原对象的前后,添加自定义的业务逻辑
降低耦合度,代理模式把原对象和调用者解耦, 使原对象更加专注自己本身的业务逻辑,非自身的逻辑可以交给代理对象处理
即使原对象还未准备好或不存在,也不影响代理对象的使用。代理对象可以在代理时再对原对象进行初始化
缺点
增加了代理类,方法调用链路变长,会增加响应时间
代码结构会变得相对复杂,增加理解成本
适用场景
需要在原有功能的前后添加自定义业务逻辑时,可以考虑使用代理模式
在需要对已有功能增加业务逻辑,而又无法拿到源码时可以考虑使用代理模式
在需要对一个很重的对象进行生命周期管理时,可以使用代理模式,比如数据库对象、Spring容器对象
与其他模式关系
-
与装饰器模式对比
代理模式和装饰器模式的结构很相似,甚至代码实现上都一致,都是将一个对象的部分工作委托给另一个对象
不同的是代理模式通常是自己管理原对象的生命周期,装饰器模式的原对象的生命周期是交给调用者来管理的
还有就是他们的目的不一样,装饰器模式目的是为了增强原对象的行为,代理模式的目的是管理原对象的行为
比如把一个孩子作为原对象,孩子需要增加一个吃饭的行为
家长喂孩子吃饭,家长就是代理对象。家长还会在孩子吃饭之前给他系上围兜,吃饭之后给他擦嘴,这是代理模式
孩子自己学习吃饭,学会了之后的这个孩子就变成了另一个对象,这是装饰器模式
装饰器模式回顾:设计模式(六):装饰器模式
-
与适配器模式对比
适配器模式是对原对象进行封装,对外提供不同的接口,从而减少不同的调用者的修改
代理模式是对原对象进行封装,对调用者提供相同的接口
适配器模式回顾:设计模式(五):适配器模式
设计模式不是万能的,只有合理利用设计模式才能写出合理的代码
-- 文章来自公众号:编程队伍 ,转载请注明出处