定义与目的

装饰器模式(Decorator Pattern)也叫包装器模式(Wrapper Pattern),在不改变原有对象的基础上,动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

原文: Attach additional responsibilities to an object dynaimcally keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.

装饰器模式主要是通过组合代替继承,从而解决继承关系过于复杂的问题。主要作用是为原始类添加增强功能,该功能是和原始功能相关的,这也是判断是否改用装饰器模式的一个重要依据。

应用场景

在买奶茶时,我们看到奶茶可以有很多种类,同样一杯饮品也可以有很多种加料(如珍珠、芋圆等)。这些加料便是一种装饰器模式
《设计模式就该这么学》中提到在代码程序中适用于以下应用场景:

  1. 用于扩展一个类的功能,或者给一个类添加附加功能。
  2. 动态地给一个对象添加功能,这些功能可以动态的被撤销。
  3. 需要为一批平行的兄弟类进行改装或加装功能。

由此更能体会到,装饰器模式用来动态地增强原始类的目的。

装饰器模式的UML类图

装饰器的UML类图如下:
uml-decorator-basea.png
由上图可见,装饰器模式一般包括4个角色:

  1. 抽象组件(Component):可以是接口或者抽象类。充当被装饰类的原始对象,规定了被装饰对象的行为。
  2. 具体组件(ConcreteComponent):一个实现/继承 Component 的具体对象,是我们将要动态地加上新型为的对象,即被装饰对象。
  3. 抽象装饰器(Decorator):装饰器共同实现的接口(或者抽象类),每个装饰器内部都有一个属性指向Component (每个组件都可以单独使用,也可以被装饰器包起来使用)。如果系统中装饰逻辑单一,并不需要实现许多装饰器,可以省略该对象,直接实现具体的装饰器即可。
  4. 具体装饰器(ComponentDecorator):Decorator具体实现类,理论上,每一个具体装饰器都扩展类Component对象的一种功能。装饰器可以加上新的方法。新的行为是通过在旧行为前面或者后面做一些计算来添加的。

由上可以看出装饰器实现原理:

  1. 装饰器与组件(即被装饰对象)拥有相同的超类,使得装饰器与被装饰对象类型一致。
  2. 装饰器构造函数中传入被装饰对象,之后装饰器中在调用被装饰对象行为前后添加相关新功能。

通常装饰器模式采用抽象类,但Java中可以使用接口。需根据实际情况决定具体使用什么,毕竟通常都在努力避免修改现有的代码。

通用写法

先看一下的通用写法:

  1. `/**
  2. * 1. 抽象组件
  3. */
  4. public abstract class Component {
  5. /**
  6. * 方法
  7. */
  8. public abstract void operation();
  9. }
  10. /**
  11. * 2. 具体组件:实现/继承类,被装饰对象
  12. */
  13. public class ConcreteComponent extends Component{
  14. @Override
  15. public void operation() {
  16. PrintUtill.println("真正的对象做一些事情");
  17. }
  18. }
  19. /**
  20. * 3. 具体装饰器A
  21. */
  22. public class ConcreteComponentA extends Decorator{
  23. /**
  24. * 构造方法:传入组件对象
  25. *
  26. * @param component
  27. */
  28. public ConcreteComponentA(Component component) {
  29. super(component);
  30. }
  31. private void operationOne() {
  32. PrintUtill.println("装饰器转发请求前的附加功能>>>>>>>>>>>>A");
  33. }
  34. private void operationTwo() {
  35. PrintUtill.println("装饰器转发请求后的附加功能>>>>>>>>>>>>A");
  36. }
  37. @Override
  38. public void operation() {
  39. operationOne(); // 添加的新功能
  40. // 可选择性调用父级方法,若不调用,则相当于完全改写了方法,实现新功能
  41. super.operation();
  42. operationTwo(); // 添加的新功能
  43. }
  44. }
  45. /**
  46. * 4. 具体装饰器B,和A类似,只是添加的新行为不同
  47. */
  48. public class ConcreteComponentB extends Decorator{
  49. // ...
  50. }
  51. /**
  52. * 客户端-用于调用测试
  53. */
  54. public class DecoratorClient {
  55. public static void main(String[] args) {
  56. ConcreteComponentA concreteComponentA = new ConcreteComponentA(new ConcreteComponent());
  57. concreteComponentA.operation();
  58. PrintUtill.printlnRule();
  59. // 嵌套修饰
  60. ConcreteComponentB concreteComponentB = new ConcreteComponentB(concreteComponentA);
  61. concreteComponentB.operation();
  62. }
  63. }

下面以制作奶茶订单为例,介绍下简单实现:

  1. /**
  2. * 1.抽象组件-奶茶
  3. * 首先有个奶茶与装饰器的共同抽象对象
  4. */
  5. public abstract class Boba {
  6. // 制作奶茶的抽象方法
  7. public abstract void make();
  8. }
  9. /**
  10. * 2. 珍珠奶茶(被装饰类)
  11. * 新增一种叫珍珠奶茶的奶茶具体实现。
  12. */
  13. public class TapiocaBoba extends Boba{
  14. @Override
  15. public void make() {
  16. PrintUtill.println("制作珍珠奶茶");
  17. }
  18. }
  19. /**
  20. * 3.抽象装饰器-用于奶茶加料
  21. */
  22. public abstract class BobaDecorator extends Boba{
  23. private Boba boba;
  24. public BobaDecorator(Boba boba) {
  25. this.boba = boba;
  26. }
  27. public void make() {
  28. // 转发制作请求给奶茶组件对象
  29. boba.make();
  30. }
  31. }
  32. /**
  33. * 4. 奶茶具体装饰器A-奶茶加料-椰果
  34. */
  35. public class CoconutJellyBobaDecorator extends BobaDecorator{
  36. public CoconutJellyBobaDecorator(Boba boba) {
  37. super(boba);
  38. }
  39. private void operationOne() {
  40. PrintUtill.println("添加配料:椰果>>>>>>>>>>>>");
  41. }
  42. @Override
  43. public void make() {
  44. super.make();
  45. operationOne();
  46. }
  47. }
  48. /**
  49. * 5.奶茶具体装饰器-奶茶加料-红豆
  50. */
  51. public class RedBeansBobaDecorator extends BobaDecorator{
  52. public RedBeansBobaDecorator(Boba boba) {
  53. super(boba);
  54. }
  55. private void operationOne() {
  56. PrintUtill.println("添加配料:红豆>>>>>>>>>>>>");
  57. }
  58. @Override
  59. public void make() {
  60. super.make();
  61. operationOne();
  62. }
  63. }
  64. /**
  65. * 客户端
  66. */
  67. public class BobaClient {
  68. public static void main(String[] args) {
  69. // 用户A定了一杯椰果珍珠奶茶
  70. CoconutJellyBobaDecorator coconutJellyBoba = new CoconutJellyBobaDecorator(new TapiocaBoba());
  71. // 制作椰果珍珠奶茶
  72. coconutJellyBoba.make();
  73. PrintUtill.printlnRule();
  74. // 用户B想要一杯椰果红豆珍珠奶茶,
  75. // 此处为简单演示嵌套装饰,省去了重新创建椰果珍珠奶茶的过程,重用上一个对象。
  76. RedBeansBobaDecorator redBeansBoba = new RedBeansBobaDecorator(coconutJellyBoba);
  77. redBeansBoba.make();
  78. }
  79. }

依赖继承VS组合

装饰器模式通过组合代替继承,主要作用是动态地为原始类添加增强功能。但通过上面的UML图以及实现代码可见,装饰器 ComponentDecorator 扩展自抽象组件 Component ,这也是用到了继承。但这里的继承是为了得到正确的类型(也即“类型匹配”),而不是继承它的行为(即利用继承获得行为)。
一般情况下依赖继承意味着继承行为,类的行为只能在编译时静态决定,。行为不是来自超类,就是子类覆盖后的版本。每当新增一个行为,都需修改现有代码(如,在奶茶中增加新配料,则可能需要每个类中都增加一个新配料对应属性)。
使用组合,可以在任何时候实现新的装饰器增加新的行为,从这方面讲是“动态地”(如,在奶茶中增加新配料,无需修改之前的代码,只需在新的装饰器中增加相应属性以及相关逻辑即可)。

总结

由上我们可以大致了解:

  1. 装饰器与被装饰对象拥有相同的超类。如此,在任何需要原始类(被装饰类)的场合,都可以用装饰过的对象代替。
  2. 可以用一个或多个装饰器(嵌套装饰)包装一个对象。
  3. 装饰器类可以在委托被装饰类的行为之前/之后,加上自己的行为,以达到特定的目的(主要作用)。
  4. 对象可以在任何时候被修饰,因此可以在运行时动态地/不限量的用喜欢的装饰器来装饰对象。

    与代理模式区别

  5. 装饰器模式是增强原始类自身功能,主体对象是被装饰类。

  6. 代理模式强调访问控制,附加的是与原始类自身功能无关的功能。代理类可以决定对功能进行扩展、缩减甚至消失(不调用真实对象的相应方法),主体对象是代理类。

    装饰器模式的优点

  7. 对继承的补充,通过组合,比继承更灵活。在不改变原有对象的情况下,动态扩展对象功能。

  8. 通过使用不同的装饰器以及这些类的排列组合(嵌套装饰),可以实现不同的效果。
  9. 装饰器模式完全遵循开闭原则。

    装饰器模式的确定

  10. 会导致设计中出现许多小对象(也会出现更多代码、更多的类),若过度使用会让程序变得更复杂。

  11. 状态装饰在多层装饰(即嵌套装饰)时会更复杂。