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

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

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

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

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

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

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

实际案例

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

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

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

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

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

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

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

Payment payment =newAliPayment();

payment.pay("赫连小伍测试支付宝支付");

payment.query("赫连小伍测试支付宝查询");

退款装饰器

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

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

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

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

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

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

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

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

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

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

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

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

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

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

实现Payment接口类,并重写pay()和query()两个方法

把Payment对象作为一个属性,并在构造器中对其进行实例化

用代码实现就是这个样子

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

RefundDecorator refundDecorator =newRefundDecorator(newAliPayment());

refundDecorator.pay("赫连小伍测试支付宝支付");

refundDecorator.query("赫连小伍测试支付宝查询");

refundDecorator.refund("赫连小伍测试支付宝退款");// 退款

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

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

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

对账适配器

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

BillDecorator billDecorator =newBillDecorator(

newRefundDecorator(

newAliPayment()));

billDecorator.pay("赫连小伍测试支付宝支付");

billDecorator.query("赫连小伍测试支付宝查询");

billDecorator.refund("赫连小伍测试支付宝退款");

billDecorator.check("赫连小伍测试支付宝对账");

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

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

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

总结

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

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

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

提供一个新的类定义需要扩展的方法,并使这个类继承或实现原有对象,让其拥有原有对象的所有行为

将原有对象作为新定义的类的属性,并为其提供实例化的方法

优点

无需修改原有对象即可扩展它的行为

可以通过多个装饰器为对象绑定不同的行为,使对象的使用更灵活

缺点

装饰器的层级递进关系比较明显,想要实现不受顺序影响的装饰器比较困难

装饰器层级比较深时,调用时套娃比较多,代码看起来不美观

与适配器模式比较

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

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

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

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容