基本概念

装饰者模式:是一种结构型设计模式,动态地将额外责任附加到对象上。对于拓展功能,装饰者提供了子类化之外的弹性替代方案,提供了比子类化更灵活的拓展方案,允许在运行时动态添加或删除对象的行为。

核心思想是:使用装饰者包装原始对象,装饰者与原始对象实现相同接口,从而在不修改原始对象的前提下,为其添加新功能。

核心结构

  1. Component(组件):定义对象接口,装饰者和被装饰者都实现此接口
  2. ConcreteComponent(具体组件):接口的具体实现类,即被装饰的原始对象
  3. Decorator(装饰者抽象类):持有组件引用,实现与组件相同的接口
  4. ConcreteDecorator(具体装饰者):实现装饰逻辑,为组件添加新功能

示例说明

 场景一:有一家咖啡连锁店,需要设计一个下单系统,其中需要设计一个拓展性强且耦合性低的咖啡设计结构,用户能够选择不同的调料,各种调教价格不一,同时存在后续新增的调料。

  1. 咖啡基类
1
2
3
4
5
6
7
8
9
10
// 咖啡基类
public abstract class Beverage {
String description = ”Unknown Beverage“;

public String getDescription(){
return description;
}

public abstract double cost();
}
  1. 改造咖啡基类,负责咖啡的基本属性,如描述等等,计算价格由饮料子类进行重写,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 浓缩咖啡
public class Espresso extends Beverage{

// 构造对象设置描述
public Espresso(){
description = “Espresso”;
}

@Override
public double cost(){
return 1.99;
}
}

  1. 饮料子类对描述进行设置,同时重新重写cost()方法。这个时候仍需要一个调料,用户可能选择多个调料,描述,价格也需要计算。新增饮料抽象类(装饰者)如下:
1
2
3
4
5
6
7
8
// 首先,我们需要和Beverage类建立继承关系,因此我们拓展Beverage类
public abstract class CondimentDecorator extends Beverage{

// 这里是每个装饰者将要包裹的Beverage对象,注意我们使用Beverage超类型来引用到Beverage,因此装饰者可以包裹任何类型的Beverage对象。
Beverage beverage;
// 所有调料装饰者都重新实现 getDescription() 方法,因为它们都包含了一个 Beverage 对象。
public abstract String getDescription();
}
  1. 有了这个装饰者抽象类,调料类只需要继承此类即可,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 摩卡调料
public class Mocha extends CondimentDecorator {

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

@Override
public String getDescription() {
return beverage.getDescription() + “, Mocha”;
}

@Override
public double cost() {
return 0.20 + beverage.cost();
}
}
  1. Whip调料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// Whip调料
public class Whip extends CondimentDecorator{

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

@Override
public String getDescription() {
return beverage.getDescription() + “, Whip”;
}

@Override
public double cost() {
return 0.10 + beverage.cost();
}
}
  1. 测试验证
1
2
3
4
5
6
7
8
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + “ $” + beverage.cost());
// 加入调料
beverage = new Mocha(beverage);
beverage = new Whip(beverage);
System.out.println(beverage.getDescription() + “ $” + beverage.cost());
}

首先创建一个咖啡对象(浓缩咖啡),使用Mocha、Whip对象进行包裹,最后可以获得一份带有Mocha、Whip调料到咖啡,这就是装饰者模式。当前更好的创建装饰者对象的方式可以用到工厂和生成器模式。

关于装饰者模式:

  • 装饰者和所装饰对象有着相同的超类型;
  • 可以用一个或多个装饰者包裹同一个对象;
  • 鉴于装饰者有着和所装饰对象相同的超类型,在需要原始对象的情况下,可以传递一个被装饰的对象;
  • 装饰者在委托给所装饰对象之前或之后添加自己的行为,来做剩下的工作;
  • 对象可以在任何时候被装饰,因此可以在运行时用任意数量的装饰者动态地装饰对象;

类图如下:
xx

总结

优点

  • 灵活扩展功能:无需修改原始类,通过组合方式动态添加功能;
  • 避免子类爆炸:比继承更高效,减少子类数量;
  • 单一职责原则:每个装饰者专注于单一功能,代码更易维护;
  • 动态组合:运行时可自由组合多个装饰者,实现复杂功能;

缺点

  • 类数量增加:大量装饰者类可能导致代码结构复杂;
  • 调试难度:多层装饰可能导致调用链变长,调试更困难;
  • 接口一致性:装饰者必须与组件实现相同接口,限制了扩展性;
  • 管理难度增加:使用装饰者模式,需要管理更多的对象,可能存在使用的遗漏,因此一般装饰者模式可以用其他模式进行创建,如工厂模式、生成器模式;

使用场景

  • Java IO流:典型的装饰者模式应用
    InputStream(组件)、FileInputStream(具体组件)
    FilterInputStream(装饰者抽象类)
    BufferedInputStreamDataInputStream(具体装饰者)