1. 装饰器模式概述

装饰器(Decorator)设计模式属于典型的结构型模式(在 GoF 的模式分类中,模式分为创建型、结构型、行为模式三种)。其核心目的在于动态地为对象添加额外功能,这也是理解装饰器模式的关键所在。

正如 GoF 在《Elements of Reusable Object-Oriented Software》中所概述:

"Decorator Pattern――Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality."

2. 应用场景:何时使用装饰器模式

在 GoF 的相关书籍中,常以文本组件与边框的例子阐述装饰器模式。这里我们以一个更具说服力的“三明治”例子来说明其必要性。

假设要为三明治小店构建程序,设计各种三明治对象。若采用传统继承方式,从简单的 Sandwich 对象开始,若要创建带蔬菜的三明治就继承原有 Sandwich 并添加蔬菜成员变量;后续若要添加咸肉、奶油等多种成分,且考虑各种成分的任意组合,那么继承方式将导致类的数量急剧膨胀。

例如,若有 n 种成分,根据组合数学知识,不同组合方案数为 2^n 种(即 $C_{n}^0 + C_{n}^1 + \cdots + C_{n}^n$)。这意味着每增加一种成分,工作量可能成倍增加,同时类库也会变得极为庞大。而装饰器模式则能有效解决此类问题,通过组合而非继承来扩展功能。

3. 设计与实现

3.1 类结构设计

  1. Ingredient(成分)
    所有类的父类,一般为抽象类且方法有默认实现,也可为接口。它包含所有成分共有的方法,如描述自身的 getDescription 方法、获取价格的 getCost 方法,以及打印自身描述和价格的 printDescription 方法(构成模板方法)。
  2. Bread(面包)
    三明治中必需的两片面包,是系统中最基本元素,也是被装饰的元素,类似 IO 中的媒质流(原始流)。它是具体成分,需实现父类抽象方法,描述可通过构造器传入,价格简单返回固定值。
  3. Decorator(装饰器)
    所有其他成分(如猪肉、羊肉、青菜、芹菜等具有装饰功能成分)的父类。这是一个抽象类,其特征为必须有一个父类(Ingredient)作为成员变量,且必须继承公共父类(Ingredient)。
  4. Concrete Decorators(具体装饰器)
    Pork(猪肉)、Mutton(羊肉)、Celery(芹菜)、GreenGrocery(青菜)等。它们继承自 Decorator 类,通过构造器传入 Ingredient 实例。在实现 getDescriptiongetCost 方法时,不仅包含自身的描述和价格,还会加上被装饰成分的描述和价格,从而增强了被装饰成分的功能。

3.2 代码实现

以下是核心类的 Java 代码实现:

Ingredient.java

public abstract class Ingredient {
    public abstract String getDescription();
    public abstract double getCost();

    public void printDescription() {
        System.out.println(" Name      " + this.getDescription());
        System.out.println(" Price RMB " + this.getCost());
    }
}

Bread.java

public class Bread extends Ingredient {
    private String description;

    public Bread(String desc) {
        this.description = desc;
    }

    public String getDescription() {
        return description;
    }

    public double getCost() {
        return 2.48;
    }
}

Decorator.java

public abstract class Decorator extends Ingredient {
    // 持有被装饰对象的引用
    Ingredient ingredient;

    public Decorator(Ingredient igd) {
        this.ingredient = igd;
    }

    public abstract String getDescription();
    public abstract double getCost();
}

Pork.java

public class Pork extends Decorator {
    public Pork(Ingredient igd) {
        super(igd);
    }

    public String getDescription() {
        String base = ingredient.getDescription();
        return base + "\n" + "Decorated with Pork!";
    }

    public double getCost() {
        double basePrice = ingredient.getCost();
        double porkPrice = 1.8;
        return basePrice + porkPrice;
    }
}

注:其他羊肉、芹菜、青菜装饰器类的实现类似,仅在描述和价格的具体数值上有所不同。

3.3 测试验证

以下是测试类 DecoratorTest,通过创建不同组合的三明治来展示装饰器模式的动态组合功能:

public class DecoratorTest {
    public static void main(String[] args) {
        // 夹羊肉的三明治
        Ingredient compound = new Mutton(new Celery(new Bread("Master24's Bread")));
        compound.printDescription();

        // 全蔬菜的三明治
        compound = new Celery(new GreenGrocery(new Bread("Bread with milk")));
        compound.printDescription();

        // 全荤的三明治
        compound = new Mutton(new Pork(new Bread("Bread with cheese")));
        compound.printDescription();
    }
}

4. 结构剖析与经典案例

4.1 通用结构

在软件设计中,常使用 UML 图来表示结构。装饰器模式的通用结构图包含以下几个关键部分:

  • Component:位于装饰器模式结构图的顶层,是定义装饰器模式中公共方法的类(对应例子中的 Ingredient)。
  • ConcreteComponent:是具体被装饰的类,类似于 IO 包中的媒体流,在“三明治”例子中就是 Bread 类。
  • Decorator:装饰器模式的核心对象,是所有具体装饰器对象的父类。它继承自 Component,同时包含一个 Component 作为其成员变量,通过这种方式实现动态地增加功能。
  • ConcreteDecoratorA 和 ConcreteDecoratorB:是两个具体的装饰器对象,完成具体的装饰功能。装饰功能的实现是通过调用被装饰对象对应的方法,再加上装饰对象自身的方法。

关于透明与不透明装饰器:
在实际情况中,存在“透明装饰器”和“不透明装饰器”之分。

  • 透明装饰器:要求整个 Decorator 结构中的所有类保持相同的“接口”(即共同方法)。
  • 不透明装饰器:其“接口”在某些子类中会得到增强,具体取决于该类与顶层抽象类或接口是否有相同的公共方法。例如 IO 中的 ByteArrayInputStreamInputStream 抽象类多一些方法,所以 IO 中的装饰器通常被视为“不透明装饰器”。

4.2 Java IO 中的装饰器

Java IO 包是装饰器模式的经典应用场景。以输入字节流部分为例,其结构如下:

  • InputStream:是装饰器的顶层类,为抽象类,包含一些共有的方法,如读方法(read,有 3 个不同参数的重载形式)、关闭流的方法(close)、mark 相关的方法(markresetmarkSupported)、跳跃方法(skip)、查询是否还有元素方法(available)。
  • FileInputStream、PipedInputStream 等:是具体的被装饰对象,它们的“接口”中一般有额外的方法。
  • FilterInputStream:是装饰器中的核心,即 Decorator 对象。
  • DataInputStream、BufferedInputStream 等:是具体的装饰器,它们保持了和 InputStream 同样的接口。
  • ObjectInputStream:是 IO 字节输入流中特殊的装饰器,它不是 FilterInputStream 的子类,但和其他 FilterInputStream 的子类功能相似,都可以装饰其他对象。

注:IO 包中的输出字节流、输入字符流和输出字符流也都采用了装饰器模式。

5. 总结与启示

装饰器模式为我们提供了一种灵活且高效的方式来扩展对象功能,避免了因大量使用继承而导致的类爆炸问题。通过动态组合装饰器对象,可以根据需求在运行时为对象添加各种功能,提高了代码的可维护性和扩展性。

在实际软件开发中,当遇到需要为对象添加多种可选功能且不希望通过复杂的继承层次来实现时,装饰器模式是一个值得考虑的优秀设计模式。它能够让我们的代码结构更加清晰,同时也便于应对不断变化的功能需求。

说明:本文基于经典 Java IO 流架构讲解装饰器模式,该模式在 Java 标准库中长期稳定存在(适用于 Java 1.0+ 及后续版本)。虽然现代 Java 开发中 NIO 应用广泛,但理解 IO 中的装饰器模式对于掌握设计模式思想依然具有重要价值。