设计模式(八):代理模式

大家好,欢迎来到编程队伍,我是作者王小伍,你可以叫我伍先生

这篇文章是设计模式系列文章的第七篇:代理模式

设计模式系列前几篇没看的可以点击对应的文章快速查看

设计模式(一):单例模式

设计模式(二):工厂模式

设计模式(三):生成器模式

设计模式(四):原型模式

设计模式(五):适配器模式

设计模式(六):装饰器模式

设计模式(七):桥接模式


正文

我们还是老规矩,用一个具体案例开始我们的设计模式之旅

假如我们程序里具备了支付功能,有一个支付接口 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
img

代理模式的实现方式主要分为三步

  1. 新建一个代理类实现接口类,并把接口类作为代理类的成员变量

  2. 在代理类中提供一个带参数的构造器,并对成员变量进行初始化

  3. 重写接口类的方法,添加自定义业务逻辑,并通过成员变量调用父类的方法

代理模式还分为静态代理和动态代理,本文例子使用的是静态代理

在设计模式系列完结之前,我会单独开一篇文章讲动态代理

为了避免本篇文章篇幅过长,也为了降低大家的学习成本,本篇文章不对动态代理做过多描述

优缺点

优点

代理模式的最大优点在于可以在调用原对象的前后,添加自定义的业务逻辑

降低耦合度,代理模式把原对象和调用者解耦, 使原对象更加专注自己本身的业务逻辑,非自身的逻辑可以交给代理对象处理

即使原对象还未准备好或不存在,也不影响代理对象的使用。代理对象可以在代理时再对原对象进行初始化

缺点

增加了代理类,方法调用链路变长,会增加响应时间

代码结构会变得相对复杂,增加理解成本

适用场景

需要在原有功能的前后添加自定义业务逻辑时,可以考虑使用代理模式

在需要对已有功能增加业务逻辑,而又无法拿到源码时可以考虑使用代理模式

在需要对一个很重的对象进行生命周期管理时,可以使用代理模式,比如数据库对象、Spring容器对象

与其他模式关系

  • 与装饰器模式对比

代理模式和装饰器模式的结构很相似,甚至代码实现上都一致,都是将一个对象的部分工作委托给另一个对象

不同的是代理模式通常是自己管理原对象的生命周期,装饰器模式的原对象的生命周期是交给调用者来管理的

还有就是他们的目的不一样,装饰器模式目的是为了增强原对象的行为,代理模式的目的是管理原对象的行为

比如把一个孩子作为原对象,孩子需要增加一个吃饭的行为

家长喂孩子吃饭,家长就是代理对象。家长还会在孩子吃饭之前给他系上围兜,吃饭之后给他擦嘴,这是代理模式

孩子自己学习吃饭,学会了之后的这个孩子就变成了另一个对象,这是装饰器模式

装饰器模式回顾:设计模式(六):装饰器模式

  • 与适配器模式对比

适配器模式是对原对象进行封装,对外提供不同的接口,从而减少不同的调用者的修改

代理模式是对原对象进行封装,对调用者提供相同的接口

适配器模式回顾:设计模式(五):适配器模式

设计模式不是万能的,只有合理利用设计模式才能写出合理的代码

-- 文章来自公众号:编程队伍 ,转载请注明出处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值