装饰者模式的应用场景

装饰者模式(Decorator Pattern)是指在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的方案(扩展原有对象的功能),属于结构型模式。装饰器模式适用于以下场景:

  1. 扩展一个类的功能或给一个类添加附加职责。
  2. 动态给一个对象添加功能,这些功能可以再动态地撤销。

来看一个这样的场景。上班族大多有睡懒觉的习惯,每天早上上班都“踩着点”,于是很多“小伙伴”为了多懒一会儿床都不吃早餐。也有些“小伙伴”可能在上班路上碰到卖煎饼的路边摊,会带一个到公司茶水间吃。卖煎饼的大姐可以给你的煎饼加鸡蛋,也可以加香肠。

下面我们用代码还原一下这个生活场景。首先创建一个煎饼类 Battercake:

  1. package com.yjw.demo.pattern.decorator2.v1;
  2. public class Battercake {
  3. protected String getMsg() {
  4. return "煎饼";
  5. }
  6. public int getPrice() {
  7. return 5;
  8. }
  9. }

然后创建一个加鸡蛋的煎饼类 BattercakeWithEgg:

  1. package com.yjw.demo.pattern.decorator2.v1;
  2. public class BattercakeWithEgg extends Battercake {
  3. @Override
  4. protected String getMsg() {
  5. return super.getMsg() + "+1个鸡蛋";
  6. }
  7. /**
  8. * 加1个鸡蛋加1元钱
  9. *
  10. * @return
  11. */
  12. @Override
  13. public int getPrice() {
  14. return super.getPrice() + 1;
  15. }
  16. }

再创建一个既加鸡蛋有加香肠的 BattercakeWithEggAndSausage 类:

  1. package com.yjw.demo.pattern.decorator2.v1;
  2. public class BattercakeWithEggAndSausage extends BattercakeWithEgg {
  3. @Override
  4. protected String getMsg() {
  5. return super.getMsg() + "+1跟香肠";
  6. }
  7. /**
  8. * 加1根香肠加2元钱
  9. *
  10. * @return
  11. */
  12. @Override
  13. public int getPrice() {
  14. return super.getPrice() + 2;
  15. }
  16. }

编写客户端测试代码:

  1. package com.yjw.demo.pattern.decorator2.v1;
  2. public class BattercakeTest {
  3. public static void main(String[] args) {
  4. Battercake battercake = new Battercake();
  5. System.out.println(battercake.getMsg() + ",总价格:" + battercake.getPrice());
  6. Battercake battercakeWithEgg = new BattercakeWithEgg();
  7. System.out.println(battercakeWithEgg.getMsg() + ",总价格:" + battercakeWithEgg.getPrice());
  8. Battercake battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();
  9. System.out.println(battercakeWithEggAndSausage.getMsg() + ",总价格:" + battercakeWithEggAndSausage.getPrice());
  10. }
  11. }

运行结果如下图所示。

image.png

运行结果没有问题。但是,如果用户需要一个加 2 个鸡蛋、加 1 根香肠的煎饼,用我们现在的类结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。如果需求再变,一直做定制显然是不科学的。下面我们就用装饰器模式来解决上面的问题。首先,创建一个煎饼的抽象类 Battercake:

  1. package com.yjw.demo.pattern.decorator2.v2;
  2. public abstract class Battercake {
  3. protected abstract String getMsg();
  4. protected abstract int getPrice();
  5. }

然后,创建一个基本煎饼类 BaseBattercake:

  1. package com.yjw.demo.pattern.decorator2.v2;
  2. public class BaseBattercake extends Battercake {
  3. @Override
  4. protected String getMsg() {
  5. return "煎饼";
  6. }
  7. @Override
  8. protected int getPrice() {
  9. return 5;
  10. }
  11. }

再创建一个扩展套餐的抽象装饰器类 BattercakeDecorator:

  1. package com.yjw.demo.pattern.decorator2.v2;
  2. public abstract class BattercakeDecorator extends Battercake {
  3. // 静态代理,委派
  4. private Battercake battercake;
  5. public BattercakeDecorator(Battercake battercake) {
  6. this.battercake = battercake;
  7. }
  8. protected abstract void doSomething();
  9. @Override
  10. protected String getMsg() {
  11. return this.battercake.getMsg();
  12. }
  13. @Override
  14. protected int getPrice() {
  15. return this.battercake.getPrice();
  16. }
  17. }

接下来,创建鸡蛋装饰器类 EggDecorator:

  1. package com.yjw.demo.pattern.decorator2.v2;
  2. public class EggDecorator extends BattercakeDecorator {
  3. public EggDecorator(Battercake battercake) {
  4. super(battercake);
  5. }
  6. @Override
  7. protected void doSomething() {
  8. }
  9. @Override
  10. protected String getMsg() {
  11. return super.getMsg() + "+1个鸡蛋";
  12. }
  13. @Override
  14. protected int getPrice() {
  15. return super.getPrice() + 1;
  16. }
  17. }

最后,创建香肠装饰器类 SausageDecorator:

  1. package com.yjw.demo.pattern.decorator2.v2;
  2. public class SausageDecorator extends BattercakeDecorator {
  3. public SausageDecorator(Battercake battercake) {
  4. super(battercake);
  5. }
  6. @Override
  7. protected void doSomething() {
  8. }
  9. @Override
  10. protected String getMsg() {
  11. return super.getMsg() + "+1根香肠";
  12. }
  13. @Override
  14. protected int getPrice() {
  15. return super.getPrice() + 2;
  16. }
  17. }

编写客户端测试代码:

  1. package com.yjw.demo.pattern.decorator2.v2;
  2. public class BattercakeTest {
  3. public static void main(String[] args) {
  4. Battercake battercake;
  5. // 路边摊买一个煎饼
  6. battercake = new BaseBattercake();
  7. // 加1个鸡蛋
  8. battercake = new EggDecorator(battercake);
  9. // 再加1个鸡蛋
  10. battercake = new EggDecorator(battercake);
  11. // 再加1根香肠
  12. battercake = new SausageDecorator(battercake);
  13. System.out.println(battercake.getMsg() + ",总价格:" + battercake.getPrice());
  14. }
  15. }

运行结果如下图所示。

image.png

装饰者模式和适配器模式对比

装饰器模式和适配器模式都是包装模式(Wrapper Pattern),装饰者模式也是一种特殊的代理模式,二者对比如下表所示。

装饰者模式 适配器模式
形式 是一种非常特别的适配器模式 没有层级关系,装饰者模式有层级关系
定义 装饰者和被装饰者实现同一个接口,主要目的是扩展之后依旧保留OOP关系 适配器和被适配者没有必然的联系,通常采用继承或代理的形式进行包装
关系 满是is-a的关系 满足has-a的关系
功能 注重覆盖、扩展 注重兼容、转换
设计 前置考虑 后置考虑

装饰者模式的优缺点

装饰者模式的优点如下:

  1. 装饰者模式是继承的有力补充,且比继承灵活,可以在不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
  2. 使用不同的装饰器类及这些装饰类的排列组合,可以实现不同的效果。
  3. 装饰器模式完全符合开闭原则。

装饰器模式的缺点如下:

  1. 会出现更多的代码、更多的类,增加程序的复杂性。
  2. 动态装饰时,多层装饰会更复杂。

摘录:《Spring 5 核心原理与30个类手写实战》来自文艺界的Tom老师的书籍。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/gn3qgn 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。