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

俄罗斯套娃大家都玩过吧,就像是这样

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

这玩意玩起来很上头,打开一个总期待会有下一个,充满了趣味性

程序员在写代码时,也会遇到像套娃这样令人上头的代码

打开一个类,里面还有一个类,再打开一个,里面还有一个...

这种套娃似的代码其实是一种很常见的设计模式,它叫装饰器模式

今天我们就来扒一扒装饰器模式到底是个什么东西

实际案例

假如我们要写一个支付的功能,支付的方式有支付宝和微信

我们用代码来实现这个功能

首先我们要定义一个接口类Payment,这个接口类用来规定支付功能应该有哪些行为,也就是应该有哪些方法

比如可以有支付查询两个方法,对应的方法名分别是pay()query()

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

然后分别定义一个支付宝类和微信类实现Payment接口类,并重写支付查询的方法

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

这样,支付功能就写好了,在需要支付的时候直接调用就可以了

比如,需要使用支付宝支付时,可以这样调用

Payment payment = new AliPayment();
payment.pay("赫连小伍测试支付宝支付");
payment.query("赫连小伍测试支付宝查询");

退款装饰器

在系统运行一段时间后,发现用户会有支付错误的情况,需要退款

这时候我们就需要新增退款功能

从实际业务考虑,退款属于整个支付功能中的一个行为。所以,应该在Payment接口类中增加退款的方法,支付宝类和微信类再分别实现这个方法

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

按照这个逻辑去修改代码,确实可以满足退款的要求,但是需要考虑一个问题

在我们的案例中只有支付宝和微信两个支付方式,真实场景中是有很多支付方式的。比如说华为支付、小米支付、云闪付、中行支付、交行支付、农行支付、工行支付等等,各种各样的支付方式加起来也有上百个

Payment接口类上增加退款方法,意味着这上百个支付方式的类都要去实现这个方法,否则代码就编译报错

工作量太大了,耗费时间太多,项目经理不可能给研发争取这么多的时间的

我们只能想别的办法,比如可以这样去实现:再定义一个类作为支付功能的扩展类,用来扩展退款功能,命名为RefundDecorator,提供一个refund()退款方法

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

它既然作为支付功能的扩展类,还应该具备支付应有的功能吧,也就是说它应该有支付查询的方法,所以它应该实现Payment接口类

实现这个接口类就意味着要重写pay()query()两个方法

重写太麻烦了,而且每个支付方式类都已经重写过这两个方法了,可以直接拿过来用。

我们把Payment类作为RefundDecorator的一个属性注入就来,这样就可以使用Payment类的pay()query()两个方法了

在使用Payment类这个属性之前,还要给它提供一个实例化的机会

基于以上要求,我们划一下重点,RefundDecorator类的修改逻辑如下

  1. 实现 Payment接口类,并重写 pay()query()两个方法
  2. Payment对象作为一个属性,并在构造器中对其进行实例化
设计模式(六):装饰器模式

用代码实现就是这个样子

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

修改过后,用户再使用支付宝支付功能时就可以这样调用

RefundDecorator refundDecorator = new RefundDecorator(new AliPayment());
refundDecorator.pay("赫连小伍测试支付宝支付");
refundDecorator.query("赫连小伍测试支付宝查询");
refundDecorator.refund("赫连小伍测试支付宝退款"); // 退款

其实,RefundDecorator类就是一个装饰器,这种编码方式就是装饰器模式

文章开头也说了,装饰器模式是套娃的,这里也看不出来啊

先别着急,我们再把这个案例延申一下

对账适配器

我们系统又运行了一段时间,用户既可以支付又可以退款了,用户很满意

可是,商家不满意了。因为系统每天统计的商家收入和商家的实际收入有差异,比如,昨天商家实际收入1万元,可是系统统计的只有9千元

商家对我们的系统极其不信任,要求我们必须尽快修复问题

这时候我们就需要引入对账功能,用来保证商家的实际收入和系统统计的收入数据一致

按照我们刚才添加退款功能时的逻辑来分析

不能在Payment类中增加对账方法,因为所有子类都需要重写该方法,工作量太大

只能新增一个账单类BillDecorator,里面提供一个对账方法check()

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

账单类BillDecorator作为支付功能的扩展类,它应该具备支付的所有能力,包括支付查询退款

退款扩展类RefundDecorator正好有这三个方法,所以让账单类直接继承退款扩展类就可以了

但是别忘了,在退款类中需要给Payment提供实例化的机会,目的在于调用Payment类的pay()query()方法

所以在账单类中也要给Payment提供实例化的机会,它才也能调用这两个方法

还有一个refund()方法不能调用,所以还要给退款方法所在的类RefundDecorator提供实例化的机会

总结一下就是账单类里面要给PaymentRefundDecorator两个类提供实例化的机会

因为RefundDecorator类中已经给Payment提供了实例化方法,所以我们只需要在账单类里面给RefundDecorator类提供实例化方法就可以了

ps:上面这段逻辑稍微有点绕,但是并不复杂,都是面向对象编程中继承的一些基础知识点,细读几遍很容易理解的

所以,账单类的代码应该这样写

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

这样我们就完成了对账功能,其实这个账单类也是一个装饰器

在需要对账的时候可以这样调用

BillDecorator billDecorator = new BillDecorator(
         new RefundDecorator(
           new AliPayment()));
billDecorator.pay("赫连小伍测试支付宝支付");
billDecorator.query("赫连小伍测试支付宝查询");
billDecorator.refund("赫连小伍测试支付宝退款");
billDecorator.check("赫连小伍测试支付宝对账");

上面的代码在new对象的时候是不是像极了套娃

如果后期需求一直不断增加,我们的代码可以一直套娃下去

套娃一时爽,一直套娃一直爽

总结

装饰器模式属于设计模式三大类型中的结构型设计模式,它的主要作用是通过将父类对象作为子类对象的属性从而给父类对象绑定新的行为

在你希望增加原有对象的行为,而又不想要修改原有对象时,可以考虑使用装饰器模式

装饰器模式的实现步骤总共有两步:

  1. 提供一个新的类定义需要扩展的方法,并使这个类继承或实现原有对象,让其拥有原有对象的所有行为
  2. 将原有对象作为新定义的类的属性,并为其提供实例化的方法

优点

  • 无需修改原有对象即可扩展它的行为
  • 可以通过多个装饰器为对象绑定不同的行为,使对象的使用更灵活

缺点

  • 装饰器的层级递进关系比较明显,想要实现不受顺序影响的装饰器比较困难
  • 装饰器层级比较深时,调用时 套娃比较多,代码看起来不美观

与适配器模式比较

装饰器在可以不改变对象接口的前提下实现对象功能的扩展,适配器模式有时还需要修改接口

装饰器可以递归实现对象行为的多层绑定,适配器不能递归

装饰器主要用来扩展对象的功能,适配器主要为对象原有的功能提供更多的使用场景

还是那句话,学会设计模式不是目的,理解设计模式隐含的设计思想才能无往不利

技术需要沉淀,我们下期再见

-- 以上内容来自公众号 赫连小伍 ,转载请注明出处

上一篇:SpringCloud学习笔记(二) 提供者和消费者


下一篇:Redis__安装