杰瑞科技汇

decorator模式 java

这是一个在 Java I/O 库中广泛使用的经典设计模式,理解它对于提升代码的可扩展性和灵活性非常有帮助。

decorator模式 java-图1
(图片来源网络,侵删)

什么是装饰器模式?

装饰器模式,也叫包装器模式,是一种结构型设计模式,它允许你向一个对象动态地添加新的行为,而无需修改该对象的类或使用继承。

核心思想: 将功能包装在对象中,而不是通过继承来扩展功能。

打个比方: 想象一下你有一杯咖啡(这是你的基础对象)。

  • 你可以加牛奶,这就给咖啡增加了“加奶”的功能。
  • 你可以再加糖,这就又增加了“加糖”的功能。
  • 你也可以先加糖,再加奶。

这里的“牛奶”和“糖”就是装饰器,它们包装了咖啡,为它增加了新的特性,你可以任意组合这些装饰器,而咖啡本身的核心代码(Coffee类)完全不需要改变。

decorator模式 java-图2
(图片来源网络,侵删)

为什么需要装饰器模式?(解决的问题)

装饰器模式主要解决了以下问题:

  1. 避免“类爆炸”问题:如果使用继承来扩展功能,MilkCoffee, SugarMilkCoffee, WhippedCreamCoffee... 类的数量会呈指数级增长,难以管理。
  2. 动态添加功能:继承是在编译时确定好的,而装饰器模式允许你在运行时动态地为对象添加或移除功能。
  3. 遵循“开闭原则” (Open/Closed Principle):软件实体应该对扩展开放,对修改关闭,装饰器模式完美地体现了这一点,你可以通过增加新的装饰器来扩展功能,而无需修改原有组件的代码。
  4. 替代继承:当“是一个”的关系(is-a)不成立时,继承就不合适了。“加糖的咖啡”不是一种咖啡,而是一个被装饰的咖啡,装饰器模式更符合这种“有一个”的关系(has-a)。

装饰器模式的结构

装饰器模式包含以下几个核心角色:

  1. 组件:定义一个对象接口,可以动态地添加职责,这是所有真实对象和装饰器的共同接口或抽象类。

    • Coffee 接口。
  2. 具体组件:定义一个对象,可以给这个对象添加一些职责,这是被装饰的原始对象。

    decorator模式 java-图3
    (图片来源网络,侵删)
    • SimpleCoffee (普通咖啡)。
  3. 装饰器:持有一个组件的引用,并实现与组件相同的接口,它的核心作用是:

    • 将请求转发给被包装的组件。
    • 在转发请求之前或之后,可以执行一些额外的操作(即添加新的功能)。
    • CoffeeDecorator (抽象装饰器)。
  4. 具体装饰器:负责给组件添加新的职责。

    • MilkDecorator (牛奶装饰器), SugarDecorator (糖装饰器)。

结构图:

      +-------------------+
      |     Component     | <-----------------------------+
      | + operation()     |                                 |
      +-------------------+                                 |
                ^                                           |
                | (实现)                                     |
                v                                           |
      +-------------------+                                 |
      |  ConcreteComponent|                                 |
      | + operation()     |                                 |
      +-------------------+                                 |
                ^                                           |
                | (被包装)                                   |
                v                                           |
      +-------------------+                                 |
      |   Decorator       |                                 |
      | + component       |                                 |
      | + operation()     |                                 |
      +-------------------+                                 |
                ^                                           |
                | (继承/组合)                               |
                v                                           |
      +-------------------+      +-------------------+      |
      | ConcreteDecoratorA|      | ConcreteDecoratorB|      |
      | + operation()     |      | + operation()     |      |
      +-------------------+      +-------------------+      |

Java 代码示例

我们用咖啡的例子来实现这个模式。

步骤 1: 定义组件接口

// Component.java
public interface Coffee {
    double getCost();    // 获取价格
    String getDescription(); // 获取描述
}

步骤 2: 创建具体组件

// SimpleCoffee.java
public class SimpleCoffee implements Coffee {
    @Override
    public double getCost() {
        return 10.0; // 一杯普通咖啡10元
    }
    @Override
    public String getDescription() {
        return "Simple Coffee";
    }
}

步骤 3: 创建抽象装饰器

// CoffeeDecorator.java
public abstract class CoffeeDecorator implements Coffee {
    // 持有一个被装饰的Coffee对象的引用
    protected Coffee decoratedCoffee;
    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
    // 默认将操作委托给被装饰的对象
    @Override
    public double getCost() {
        return decoratedCoffee.getCost();
    }
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }
}

步骤 4: 创建具体装饰器

// MilkDecorator.java
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    @Override
    public double getCost() {
        // 在原有价格基础上增加2元
        return super.getCost() + 2.0;
    }
    @Override
    public String getDescription() {
        // 在原有描述基础上增加内容
        return super.getDescription() + ", with Milk";
    }
}
// SugarDecorator.java
public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }
    @Override
    public double getCost() {
        // 在原有价格基础上增加1元
        return super.getCost() + 1.0;
    }
    @Override
    public String getDescription() {
        // 在原有描述基础上增加内容
        return super.getDescription() + ", with Sugar";
    }
}

步骤 5: 客户端使用

// Client.java
public class CoffeeShop {
    public static void main(String[] args) {
        // 1. 点一杯普通咖啡
        Coffee simpleCoffee = new SimpleCoffee();
        System.out.println("Order: " + simpleCoffee.getDescription());
        System.out.println("Price: " + simpleCoffee.getCost());
        System.out.println("--------------------");
        // 2. 点一杯加牛奶的咖啡
        // 用 MilkDecorator 包装 SimpleCoffee
        Coffee milkCoffee = new MilkDecorator(simpleCoffee);
        System.out.println("Order: " + milkCoffee.getDescription());
        System.out.println("Price: " + milkCoffee.getCost());
        System.out.println("--------------------");
        // 3. 点一杯加糖又加牛奶的咖啡
        // 用 SugarDecorator 包装 MilkDecorator
        // (也就是用 SugarDecorator 包装了 SimpleCoffee)
        Coffee sugarAndMilkCoffee = new SugarDecorator(milkCoffee);
        // 或者一步到位: Coffee sugarAndMilkCoffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
        System.out.println("Order: " + sugarAndMilkCoffee.getDescription());
        System.out.println("Price: " + sugarAndMilkCoffee.getCost());
        System.out.println("--------------------");
    }
}

输出结果:

Order: Simple Coffee
Price: 10.0
--------------------
Order: Simple Coffee, with Milk
Price: 12.0
--------------------
Order: Simple Coffee, with Milk, with Sugar
Price: 13.0
--------------------

装饰器模式在 Java I/O 中的应用

装饰器模式最经典的例子就是 Java 的 I/O 流库。

  • 组件InputStream, OutputStream 等抽象类。
  • 具体组件FileInputStream, FileOutputStream, ByteArrayInputStream 等,它们是底层的、最原始的流。
  • 装饰器FilterInputStream, FilterOutputStream,它们持有一个 InputStreamOutputStream 的引用,并扩展了它们的功能。
  • 具体装饰器
    • BufferedInputStream: 为输入流添加缓冲功能,提高读取效率。
    • DataInputStream: 允许应用程序以与机器无关的方式从底层输入流中读取基本 Java 数据类型。
    • BufferedOutputStream: 为输出流添加缓冲功能。
    • GZIPOutputStream: 为输出流添加压缩功能。

使用示例:

// 读取一个文件,并使用缓冲和字符转换功能
// 1. 底层节点流 (具体组件)
FileInputStream fis = new FileInputStream("test.txt");
// 2. 装饰器1: 缓冲流 (BufferedInputStream装饰了FileInputStream)
BufferedInputStream bis = new BufferedInputStream(fis);
// 3. 装饰器2: InputStreamReader (装饰了BufferedInputStream)
//    它将字节流转换为字符流
InputStreamReader isr = new InputStreamReader(bis);
// 4. 装饰器3: BufferedReader (装饰了InputStreamReader)
//    它为字符流添加了缓冲和按行读取的功能
BufferedReader reader = new BufferedReader(isr);
String line = reader.readLine();
System.out.println(line);
reader.close();

在这个例子中,BufferedReader 装饰了 InputStreamReaderInputStreamReader 又装饰了 BufferedInputStream,而 BufferedInputStream 最终装饰了 FileInputStream,你可以像叠俄罗斯套娃一样,灵活地组合这些功能,而 FileInputStream 本身的代码完全不需要改变。


优点与缺点

优点:

  1. 高度灵活和可扩展:可以任意组合装饰器来创建复杂的功能。
  2. 遵循开闭原则:无需修改现有代码即可扩展新功能。
  3. 避免继承的弊端:避免了由于使用继承而产生的类数量爆炸问题。

缺点:

  1. 增加复杂性:相比简单的继承,装饰器模式需要创建更多的类,可能会使系统设计变得复杂。
  2. 排他性:装饰器模式通常只能用于具有相同接口的场景,如果需要装饰的对象接口差异很大,装饰器模式就不适用了。
  3. 调试困难:由于对象被多层包装,在调试时追踪对象的实际行为和来源可能会比较困难。

装饰器模式是一个非常强大且实用的设计模式,它提供了一种比继承更灵活、更优雅的方式来扩展对象的功能,它的核心在于组合委托,通过将对象包装在装饰器中,动态地为其添加新的行为。

记住它的关键点:

  • 组件接口:定义共同契约。
  • 具体组件:被装饰的原始对象。
  • 装饰器:实现组件接口,持有一个组件引用,并可以添加新行为。
  • 具体装饰器:实现具体的装饰逻辑。

下次当你需要为一个类添加新功能,但又不想通过继承来创建一大堆子类时,就应该考虑使用装饰器模式。

分享:
扫描分享到社交APP
上一篇
下一篇