装饰模式

对象结构型模式

模式动机

有两种方式可以给类增加行为:继承或者关联

  • 继承:使子类同时拥有父类方法和自己的方法
  • 关联:把一个类的对象嵌入到另一个对象中(比如组合关系),由另一个对象决定是否调用嵌入对象的行为以扩展自己的行为

装饰模式以对客户端来说透明的方式动态地给一个对象附加更多责任,即装饰前后的对象在客户端眼里是一样的。装饰模式可以在不需要创建更多子类的情况下扩展对象功能。

应用

装饰模式动态的给对象增加额外职责,比直接生成子类更灵活。装饰模式的别名是包装器,和适配器模式别名相同,但他们用于不同场合。

装饰模式包含如下角色:

  • 抽象构件Component
  • 具体构件ConcreteComponent
  • 抽象装饰类Decorator
  • 具体装饰类ConcreteDecorator

与继承相比,关联关系的优势是不会破坏类的封装性,因为继承的耦合比较大。开发阶段关联与继承的代码量差别不大,但是维护阶段关联关系的松耦合性使系统更容易维护。当然,关联关系的缺点就是要创建更多对象。

扩展系统时使用装饰模式比使用继承更加灵活

典型抽象装饰类代码如下:

public class Decorator extends Component {
private Component component;
public Decorator(Component component) {
this.component=component;
}
public void operation() {
component.operation();
}
}

典型具体装饰类代码如下:

public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation();
addedBehavior();
}
public void addedBehavior() {
// 新增方法
}
}

实例:变形金刚

变形金刚在变形之前是一辆汽车,它可以在陆地上移动。当它变成机器人之后除了能够在陆地上移动之外,还可以说话;如果需要,它还可以变成飞机,除了在陆地上移动还可以在天空中飞翔。即他的行为会发生动态变化,但在但在调用者眼里一直都是变形金刚

实例:多重加密系统

某系统提供了一个数据加密功能,可以对字符串进行加密。最简单的加密算法通过对字母进行移位来实现,同时还提供了稍复杂的逆向输出加密,还提供了更为高级的求模加密。用户先使用最简单的加密算法对字符串进行加密,如果觉得还不够可以对加密之后的结果使用其他加密算法进行二次加密,当然也可以进行第三次加密。不管加了几次密,每次加密之前这个“字符串”在用户眼里都是等价的。每次加密可以视为一次“装饰”

优点

  1. 装饰模式下的扩展比继承更灵活
  2. 可以以动态的方式扩展对象功能,可以通过配置文件在运行时选择不同装饰器,从而实现不同行为
  3. 通过使用不同具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合
  4. 具体构件类和具体装饰类可以独立变化,且新增时不会修改原有代码,符合开闭原则

缺点

  1. 在系统中产生很多小对象
  2. 作为灵活性的代价,装饰模式比继承更容易出错,排错也很麻烦,多次装饰的对象debug的时候可能要逐级排查

适用场合

  • 在不影响其它对象的情况下,希望以动态、透明的方式给单个对象添加职责
  • 需要动态地给对象增加功能(动态指运行时选择不同的具体装饰器),也可以动态撤销功能
  • 当不能采用继承对系统进行扩展时或采用继承不利于系统扩展和维护,以下两种是不能采用继承的主要情况:
    • 系统存在大量独立扩展,如果用继承支持每一种组合将产生大量子类
    • 类定义无法继承,比如被final修饰

装饰模式在Java中最经典的例子是JavaIO,在初学者眼里JavaIO简直就是“咒语”,很冗长,一层套一层:

其中:

  • 抽象装饰类:FilterInputStream
  • 抽象构件类:InputStream
  • 具体构件类:FileInputStream等
  • 具体装饰类:BufferedInputStream等

模式扩展

装饰模式简化

如果只有一个具体构件类而没有抽象构件类,那么装饰类可以作为具体构件类的直接子类

需要注意的问题

  1. 一个装饰类的接口必须和被装饰类接口保持相同,因为在客户端眼里装饰前后的对象应该是被一致对待的(无法感知变化,因为行为被封装好了)
  2. 尽量保持具体构件类Component是一个**“轻”类**,即不要把太多逻辑和状态放到具体构件类里,而是通过装饰类对其进行扩展(即用装饰类装饰具体构件类来为这个轻量级类添加新的行为)

透明装饰模式(比如多重加密系统)

要求客户端完全面向抽象(接口)编程,即完全面向抽象构件类和抽象装饰类编程,不应该声明具体类的引用,应该全部声明为抽象构建类型。这样才是完全透明的,客户端无法感知具体类的变化,从而一致的对待

Cipher sc, cc, ac; // 全都是抽象装饰类引用
sc = new SimpleCipher();
cc = new ComplexCipher(sc);
ac = new AdvancedCipher(cc);

半透明装饰模式(比如变形金刚)

允许客户端声明一些具体装饰类引用,从而可以调用具体装饰类中新增的方法(抽象装饰类中没有,想调用只能声明具体装饰类)

Transform camaro; // 抽象装饰类引用
camaro = new Car();
camaro.move(); // move是都有的
Robot bumblebee = new Robot(camaro); // 具体装饰类引用
bumblebee.move();
bumblebee.say(); // 但是say只有Robot有,所以声明了一个具体装饰类Robot

(•‿•)