设计模式系列前几篇没看的可以点击对应的文章快速查看
正文
我们还是老规矩,用一个具体案例开始我们的设计模式之旅
假如我们程序里具备了支付功能,有一个支付接口 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容器对象
与其他模式关系
与装饰器模式对比
代理模式和装饰器模式的结构很相似,甚至代码实现上都一致,都是将一个对象的部分工作委托给另一个对象
不同的是代理模式通常是自己管理原对象的生命周期,装饰器模式的原对象的生命周期是交给调用者来管理的
还有就是他们的目的不一样,装饰器模式目的是为了增强原对象的行为,代理模式的目的是管理原对象的行为
比如把一个孩子作为原对象,孩子需要增加一个吃饭的行为
家长喂孩子吃饭,家长就是代理对象。家长还会在孩子吃饭之前给他系上围兜,吃饭之后给他擦嘴,这是代理模式
孩子自己学习吃饭,学会了之后的这个孩子就变成了另一个对象,这是装饰器模式