装饰者模式是什么?

装饰者模式(Decorator Pattern)是一种结构型的设计模式,它的作用是动态地将责任附加到对象上。装饰模式遵循的是“聚合优于继承”的设计原则。如果要扩展功能,装饰者提供了一种比继承更为灵活的选择。
如何动态地将责任附加到对象上呢?举个例子:穿衣服。早上出门前,如果天气比较冷,你可以穿上一件毛衣。穿上毛衣之后还觉得冷的话,你可以再套上一件夹克。如果碰到雨天,你还可以再穿上一件雨衣。这些衣物都“装饰”了你,它们“扩展”了你的基本行为,但却不是你的一部分,如果你不再需要某件衣服,随时可以脱掉。
image.png

UML 类图

用 UML 类图来描述组合模式的结构,在模式中各个角色之间的关系:
image.png
根据上图,总结了模式中各个角色的职责以及它们之间的关系:

  • 部件声明了装饰者和被装饰者的共同接口。
  • 具体部件是那些要被修饰的对象的类,它定义了对象的基本行为,但装饰类可以增强这些行为。
  • 基础装饰者一般是一个抽象类,它不仅继承了部件,而且还拥有一个指向被装饰对象的引用。
  • 具体装饰者是基础装饰者的实现,它可动态地增强被装饰对象的行为。

案例

让我们通过一个案例来帮助我们进一步理解装饰者模式。考虑这样一个情景:有一家甜品店,店里售卖的甜品有蛋糕、面包、饼干等,顾客可以在选购甜品的时候选择是否添加酱汁,当然这需要额外的收费。甜品店的点餐系统要根据顾客选择的甜品以及添加的酱汁(如果有的话)计算最终的价格。
甜品店有各式各样的甜品:蛋糕、面包、饼干等,所以我们定义一个 Dessert 类来描述这些甜品的共性。

  1. public abstract class Dessert {
  2. public abstract String getDescription();
  3. public abstract BigDecimal cost();
  4. public void display() {
  5. System.out.println(getDescription() + ", $" + cost());
  6. }
  7. }

基于 Dessert 类,可以为蛋糕、面包等创建具体的类。

  1. public class Cake extends Dessert {
  2. @Override
  3. public String getDescription() {
  4. return "Cake";
  5. }
  6. @Override
  7. public BigDecimal cost() {
  8. return new BigDecimal("9.98");
  9. }
  10. }
  11. public class Bread extends Dessert {
  12. @Override
  13. public String getDescription() {
  14. return "Bread";
  15. }
  16. @Override
  17. public BigDecimal cost() {
  18. return new BigDecimal("5.0");
  19. }
  20. }

但是,顾客选择了酱汁的话,该如何设计呢?或许我们可以考虑继承:

  1. public class BlueberryJamCake extends Cake {
  2. // ...
  3. }
  4. public class PeanutButterBread extends Bread {
  5. // ...
  6. }
  7. // ...

但这样真的好吗?各种甜品与酱汁的组合会引起类的数量爆炸式增长,当甜品店设计出一款新的甜品或酱汁时,我们不得不为其再设计大量的类,真是令人抓狂。
我们重新缕一下思路:顾客在购买甜品时,可以选择添加酱汁,也可以选择不添加酱汁,可以选择添加一种酱汁也可以选两种酱汁,这是一种动态地变化,而最终甜品的价格也是基于这种变化计算的。装饰者模式正是解决这类问题的最佳实践,遵循该模式的思想,我们决定抛弃继承的方式,而是把酱汁当作是一个“包装者”,当顾客选择为某款甜品添加某种酱汁时,就可以理解成用这种酱汁“装饰”了这款甜品。
现在,我们根据装饰者模式来设计装饰者酱汁类,它是对所有具体装饰者(花生酱、番茄酱等)的抽象,它要继承甜品类 Dessert 以保持有与之相同的接口,同时它还包含一个 Dessert 类的属性,这就是它要修饰的对象。

  1. public abstract class Sauce extends Dessert {
  2. protected Dessert dessert;
  3. public Sauce(Dessert dessert) {
  4. this.dessert = dessert;
  5. }
  6. @Override
  7. public String getDescription() {
  8. return dessert.getDescription();
  9. }
  10. @Override
  11. public BigDecimal cost() {
  12. return dessert.cost();
  13. }
  14. }

基于 Sauce 类,可以为花生酱、番茄酱等创建具体的酱汁类(装饰者)。

  1. public class PeanutButter extends Sauce {
  2. // constructor
  3. @Override
  4. public String getDescription() {
  5. return super.getDescription() + " with peanut butter";
  6. }
  7. @Override
  8. public BigDecimal cost() {
  9. return super.cost().add(new BigDecimal("1.5"));
  10. }
  11. }
  12. public class TomatoSauce extends Sauce {
  13. // constructor
  14. @Override
  15. public String getDescription() {
  16. return super.getDescription() + " with tomato sauce";
  17. }
  18. @Override
  19. public BigDecimal cost() {
  20. return super.cost().add(new BigDecimal("1.0"));
  21. }
  22. }
  23. public class MustardSauce extends Sauce {
  24. // constructor
  25. @Override
  26. public String getDescription() {
  27. return super.getDescription() + " with mustard sauce";
  28. }
  29. @Override
  30. public BigDecimal cost() {
  31. return super.cost().add(new BigDecimal("2.99"));
  32. }
  33. }

最后,我们通过一个用例来模拟一下顾客点餐的情景:

public class DecoratorMain {
    public static void main(String[] args) {
        Dessert cake = new Cake();
        cake.display();

        Dessert mustardCake = new MustardSauce(new Cake()) ;
        mustardCake.display();

        Dessert peanutTomatoBread = new PeanutButter(new TomatoSauce(new Bread()));
        peanutTomatoBread.display();
    }
}

案例源码

可在 GitHub 上查看上述案例的完整代码。

参考资料

本文参考的资料如下: