设计模式:装饰者模式

装饰者模式

提出问题

  • 动态地给一个对象添加一些额外的职责。

问题案例

想买一个加糖的饮料:

  • 则需要实现饮料类。
  • 其次实现加糖的饮料,并继承自饮料类,即一个加糖的饮料类。
  • 如果还想加可可,则需要再次实现一个类,是一个加糖、加可可的类。

对于这种动态的增加,并且增加的数目不确定,使用继承实现会出现太多的类。

基础概述

是什么

定义:动态地将责任附加到对象上,想要扩展功能,装饰者提供有别于继承的另一种选择。

装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。

img

分类

以饮料为例:

  • 装饰者:调料,Decorator。
  • 具体组件:饮料,ConcreteCompoent。

饮料可以动态添加新的调料,添加新的功能并不需要修改代码。

应用

适用性

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
    • 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同的效果。
  • 这些职责还可以动态地撤销。
  • 提供了比继承更有弹性的替代方案。
    • 继承在实现时就已经确定了。
  • 当不能采用生成子类的方法进行扩充时。
    • 如可能有大量独立的扩展,每一种组合将产生大量的子类,使得子类数目爆炸增长。
    • 可能因为类定义被隐藏或类定义不能用来生成子类。

应用场景

协作

结构

1566438131434

参与者

  • Component。定义一个对象接口,可以给这些对象动态地添加职责。
  • ConcreteComponent。定义一个对象,可以给这个对象添加就一些职责。
  • Decorator。维持一个指向Compinent对象的指针,并定义一个与Component接口一致的接口。
  • ConcreteDecorator。向组件添加职责。

协作

  • 类关系。
  • 逻辑关系。
    • Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作。

权衡

优点:

  • 比静态继承更为灵活,提供了更灵活的向对象添加职责的方式,可以采用添加和分离的方式,用装饰在运行时刻增加和删除职责。
    • 继承要求为每个添加的职责创建一个新的子类,增加了系统复杂性。
  • 避免在层次结构高层具有太多的特征。Decorator模式提供了一种即用即付的方式添加职责。

缺点:

  • 会出现更多的代码,更多的类,增加程序复杂性。
  • 动态装饰时,多层装饰时会更复杂。
    • 比继承而言,类可能会少但对象会更多。

实现

注意

  • 接口的一致性。装饰对象的接口必须与它所装饰的Component的接口是一致的,因此所有的ConcreteDecorator类都必须有一个公共的父类。
  • 省略抽象的Decorator。如果只添加一个职责,则没有必要定义抽象。
  • 保持Component类的简单性。
  • 改变对象外壳与改变对象内核。可以将Decorator看作一个对象的外壳,它可以改变这个对象的行为。另一种方式是改变对象的内核,即Strategy。
    • 当Component很庞大,则使用Decorator代价太高,此时应该使用Strategy模式。

实现步骤

  • 设计父类接口Component:
    • 有着公有的一个方法。
  • 设计装饰者Decorator实现接口Component。
  • 设计具体组件抽象类ConcreteComponet实现接口Component。
  • 实现具体组件继承自抽象类:
    • 类可以自由添加Decorator,可通过set或者构造器注入。

装饰者即与需要装饰的对象实现同一个接口,并且在装饰者当中留有一个接口的引用用于引用被装饰对象。

示例

设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。

下图表示在DarkRoast饮料上新增新添加Mocha配料,之后又添加了Whip配料。DarkRoast被Mocha包裹,Mocha又被Whip包裹。它们都继承自相同父类,都有cost()方法,外层类的cost()方法调用了内层类的cost()方法。

img

1
2
3
4
//最上层统一接口
public interface Beverage {
double cost();
}
1
2
3
4
5
6
7
//装饰者
public class HouseBlend implements Beverage {
@Override
public double cost() {
return 1;
}
}
1
2
3
4
5
//组件抽象类
public abstract class CondimentDecorator implements Beverage {
//需要包装的装饰者
protected Beverage beverage;
}
1
2
3
4
5
6
7
8
9
10
11
public class Milk extends CondimentDecorator {

public Milk(Beverage beverage) {
this.beverage = beverage;
}

@Override
public double cost() {
return 1 + beverage.cost();
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Mocha extends CondimentDecorator {

public Mocha(Beverage beverage) {
this.beverage = beverage;
}

@Override
public double cost() {
return 1 + beverage.cost();
}
}
1
2
3
4
5
6
7
8
9
10
public class Client {

public static void main(String[] args) {
Beverage beverage = new HouseBlend();
beverage = new Mocha(beverage);
beverage = new Milk(beverage);
System.out.println(beverage.cost());
}
}
3.0

相关模式

  • Adapter。装饰只改变对象的职责而不改变接口,适配器将给对象一个全新的接口。
  • Composite。可以将装饰视为一个退化的,仅有一个组件的组合,装饰仅给对象添加一些额外的职责,它的目标不在于对象聚集。
  • Strategy。策略模式使得可以改变对象的内核,装饰使得可以改变对象的外表。

进阶

反省总结

参考