1、定义
在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰者来包裹真实的对象。所以装饰者可以动态地给一个对象添加一个额外的职责。就增加功能来说,装饰者模式相比生成子类更加灵活。
2、模式结构
装饰者模式由四部分组成:
- Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
- ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰者可以给它增加额外的职责(方法)。
- Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
- ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
3、实例
3.1 Coffee抽象类(Component)
public abstract class Coffee {
public String description;
public String getDescription() {
return description;
}
public void serDescription(String description) {
this.description = description;
}
public abstract double cost();
}
3.2 Coffee类(ConcreteComponent)
public class Mocha extends Coffee {
public Mocha() {
description = "Mocha";
}
@Override
public double cost() {
return 15.90;
}
}
public class Espresso extends Coffee {
public Espresso() {
description = "Espresso";
}
@Override
public double cost() {
return 12.50;
}
}
3.3 调料CondimentDecorator抽象类(Decorator)
public abstract class CondimentDecorator extends Coffee {
@Override
public abstract String getDescription();
}
3.4 调料类(ConcreteDecorator)
public class Milk extends CondimentDecorator {
private Coffee coffee;
public Milk(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Mike";
}
@Override
public double cost() {
return coffee.cost() + 2.0;
}
}
public class Sugar extends CondimentDecorator {
private Coffee coffee;
public Sugar(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
@Override
public double cost() {
return coffee.cost() + 1.0;
}
}
3.5 客户端调用
public class Client {
public static void main(String arg[]) {
Coffee mocha = new Mocha();
mocha = new Sugar(mocha);
mocha = new Sugar(mocha);
System.out.println(mocha.getDescription() + "$" + mocha.cost());
Coffee espresso = new Espresso();
espresso = new Milk(espresso);
espresso = new Sugar(espresso);
System.out.println(espresso.getDescription() + "$" + espresso.cost());
}
}
4、适用场景
- 需要扩展一个类的功能,或给一个类添加附加职责。
- 需要动态的给一个对象添加功能,这些功能可以在动态的撤销。
- 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
- 当不能采用生成子类的方法进行扩充时。一种情况是,可以由大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
5、在Java IO流中的应用
6、优缺点
6.1 优点
- 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。
- 被装饰者与装饰者解耦,被装饰者可以不知道装饰者的存在,同时新增功能时原有代码也无需改变,符合“开闭原则”。
6.2 缺点
- 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
- 装饰模式比继承更加灵活机动的特性,同时也意味着比继承更加易于出错,拍错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。
- 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,已经装饰者是否合适。
7、透明装饰模式和半透明装饰模式
7.1 透明装饰模式
在透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件抽象。
7.2 半透明装饰模式
透明装饰模式的设计难度较大,而且有时我们需要单独调用新增的业务方法。为了能够调用新增方法,我们不得不用具体装饰类型来定义装饰之后的对象,而具体构件类型还是可以使用抽象构件类型来定义,这种装饰模式即为半透明装饰模式。
半透明装饰模式可以给系统带来更多的灵活性,设计相当简单,使用起来也非常方便;但是其最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象。