教程中心 / Java教程 / 装饰器模式

装饰器模式

10分钟 设计模式

什么是装饰器模式?

装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许在运行时动态地给对象添加额外功能。与继承不同,装饰器模式更加灵活,可以在不修改原有类的情况下扩展功能。

生活比喻

想象你买手机。基础款手机只有打电话功能。但你可以加钱"装饰"它:贴膜(防刮)、加手机壳(防摔)、装内存卡(扩展存储)。这些都是"装饰器",可以自由组合,按需加装。装饰器模式就是这样——在对象外面包一层,动态添加功能。

装饰器模式结构:

        Component(组件接口)
             │
    ┌────────┴────────┐
    ▼                 ▼
ConcreteComponent   Decorator(装饰器)
 (基础对象)          │
                    │ 持有Component引用
                    ▼
            ┌───────────────┐
            │ConcreteDecorator│
            │ (具体装饰器)   │
            │ + 添加的功能   │
            └───────────────┘

咖啡例子:
        Coffee (接口)
             │
    ┌────────┴────────┐
    ▼                 ▼
SimpleCoffee     CoffeeDecorator
(原味咖啡)           │
                    ▼
            ┌───────────────┐
            │ MilkDecorator │
            │ +牛奶         │
            └───────────────┘

为什么需要装饰器模式?

继承的局限性

// 如果用继承扩展咖啡类...
class SimpleCoffee { double getCost() { return 2.0; } }
class MilkCoffee { double getCost() { return 2.5; } }
class SugarCoffee { double getCost() { return 2.2; } }
class MilkSugarCoffee { double getCost() { return 2.7; } }
// 牛奶+糖+摩卡+... 类的数量爆炸!

如果用装饰器模式,只需要定义基础组件和装饰器,自由组合:

// 咖啡 + 牛奶 + 糖 + 摩卡 = MilkSugarMochaCoffee
Coffee coffee = new MochaDecorator(new SugarDecorator(new MilkDecorator(new SimpleCoffee())));

完整实现

1. 定义组件接口

// 组件接口 - 定义组件和装饰器的公共接口
public interface Coffee {
    String getDescription();
    double getCost();
}

2. 实现具体组件

// 基础组件 - 最简单的咖啡
public class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "原味咖啡";
    }

    @Override
    public double getCost() {
        return 2.0;
    }
}

3. 创建装饰器基类

// 装饰器基类 - 持有组件引用,实现组件接口
public abstract class CoffeeDecorator implements Coffee {
    // 持有被装饰的对象
    protected Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return coffee.getDescription();
    }

    @Override
    public double getCost() {
        return coffee.getCost();
    }
}

4. 实现具体装饰器

// 牛奶装饰器
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + " + 牛奶";
    }

    @Override
    public double getCost() {
        return coffee.getCost() + 0.5;
    }
}

// 糖装饰器
public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + " + 糖";
    }

    @Override
    public double getCost() {
        return coffee.getCost() + 0.2;
    }
}

// 摩卡装饰器
public class MochaDecorator extends CoffeeDecorator {
    public MochaDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + " + 摩卡";
    }

    @Override
    public double getCost() {
        return coffee.getCost() + 1.0;
    }
}

// 奶油装饰器
public class WhipDecorator extends CoffeeDecorator {
    public WhipDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + " + 奶油";
    }

    @Override
    public double getCost() {
        return coffee.getCost() + 0.8;
    }
}

5. 客户端使用

public class DecoratorDemo {
    public static void main(String[] args) {
        System.out.println("===== 点一杯原味咖啡 =====");
        Coffee coffee1 = new SimpleCoffee();
        printCoffee(coffee1);

        System.out.println("\n===== 点一杯拿铁(咖啡+牛奶)=====");
        Coffee coffee2 = new MilkDecorator(new SimpleCoffee());
        printCoffee(coffee2);

        System.out.println("\n===== 点一杯特调(咖啡+牛奶+糖+摩卡)=====");
        Coffee coffee3 = new MochaDecorator(
                          new SugarDecorator(
                          new MilkDecorator(
                          new SimpleCoffee())));
        printCoffee(coffee3);

        System.out.println("\n===== 摩卡星冰乐(摩卡+奶油+糖)=====");
        Coffee coffee4 = new WhipDecorator(
                          new MochaDecorator(
                          new SugarDecorator(
                          new SimpleCoffee())));
        printCoffee(coffee4);
    }

    static void printCoffee(Coffee coffee) {
        System.out.println("描述: " + coffee.getDescription());
        System.out.println("价格: $" + coffee.getCost());
    }
}

Java IO中的装饰器模式

Java的I/O流大量使用了装饰器模式:

Java IO装饰器模式:

┌─────────────────────────────────────────────────────────┐
│  组件接口: InputStream / OutputStream                   │
└─────────────────────────────────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────┐
        ▼                   ▼                   ▼
┌───────────┐       ┌───────────┐       ┌───────────┐
│ FileInput │       │ ByteArray │       │  ...其他  │
│   Stream   │       │ InputStream│       │           │
└─────┬─────┘       └───────────┘       └───────────┘
      │                   │
      └─────────┬─────────┘
                │ 被装饰对象
        ┌───────▼───────┐
        │ FilterInput   │  ← 装饰器基类
        │   Stream      │
        └───────┬───────┘
                │ 装饰器
    ┌───────────┼───────────┬───────────┐
    ▼           ▼           ▼           ▼
┌───────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│Buffered│ │Data      │ │GZIP     │ │  ...其他 │
│Input   │ │InputStream│ │InputStream│ │装饰器   │
│缓冲    │ │数据流    │ │压缩流   │ │          │
└───────┘ └──────────┘ └─────────┘ └──────────┘
import java.io.*;

// 装饰器模式在IO中的应用
public class IODecoratorDemo {
    public static void main(String[] args) throws Exception {
        // 基础:文件输入流
        InputStream fis = new FileInputStream("test.txt");

        // 装饰1:缓冲流(加速读取)
        BufferedInputStream bis = new BufferedInputStream(fis);

        // 装饰2:数据输入流(方便读取数据类型)
        DataInputStream dis = new DataInputStream(bis);

        // 装饰3:压缩流(自动解压)
        GZIPInputStream gis = new GZIPInputStream(dis);

        // 现在可以从压缩文件中方便地读取各种类型的数据
        // String name = dis.readUTF();
        // int age = dis.readInt();
    }
}

装饰器模式的优势

开闭原则

扩展功能不需要修改原有代码

自由组合

可以任意组合装饰器,按需添加

单一职责

每个装饰器只负责一种扩展功能

运行时生效

可以在运行时动态添加/移除装饰

装饰器 vs 继承

对比项继承装饰器
编译时编译时确定运行时动态组合
类数量组合爆炸类数量稳定
灵活性
扩展需要修改代码新增装饰器即可

小结

  • 装饰器模式动态给对象添加功能,比继承更灵活
  • 装饰器持有组件引用,调用被装饰对象的方法并增强
  • 可以叠加多个装饰器,自由组合
  • 符合开闭原则,不修改原有代码
  • Java IO流大量使用装饰器模式
☕ Java 在线代码编辑器
📝 运行结果