装饰模式(Decorator Pattern)是结构型设计模式里很有代表性的一员,它的核心思想是:在不改变原有对象结构的情况下,动态地给对象添加新的功能

有点像在奶茶里加珍珠加布丁,不需要重新定义「珍珠奶茶类」「布丁奶茶类」,而是通过一层层“装饰”来实现。


一、为什么需要装饰模式?

假设你有一个 MilkTea 接口:

public interface MilkTea {
    String getDescription();
    double cost();
}

如果用户要点「珍珠奶茶」「布丁奶茶」「珍珠布丁奶茶」,直观的做法是写很多子类:

  • PearlMilkTea

  • PuddingMilkTea

  • PearlAndPuddingMilkTea
    ……

这会导致 类爆炸问题,因为每多一种配料,组合数量就指数级增加。

装饰模式就解决了这个问题:我们不在继承树上不断扩展,而是通过组合(composition)和包装(wrapping)来动态增强对象功能


二、装饰模式的结构

装饰模式有四个核心角色:

  1. Component(组件接口)
    定义对象的抽象接口,比如 MilkTea

  2. ConcreteComponent(具体组件)
    实现接口的基本功能,比如 SimpleMilkTea

  3. Decorator(装饰抽象类)
    持有一个 Component 引用,并且实现相同接口,用来“套娃”。

  4. ConcreteDecorator(具体装饰类)
    在调用被装饰对象的方法基础上,增加新行为,比如 PearlDecorator、PuddingDecorator


三、代码示例(奶茶)

1. 组件接口
public interface MilkTea {
    String getDescription();
    double cost();
}

2. 具体组件:基础奶茶
public class SimpleMilkTea implements MilkTea {
    @Override
    public String getDescription() {
        return "原味奶茶";
    }

    @Override
    public double cost() {
        return 8.0; // 基础价格
    }
}

3. 抽象装饰类
public abstract class MilkTeaDecorator implements MilkTea {
    protected MilkTea milkTea;

    public MilkTeaDecorator(MilkTea milkTea) {
        this.milkTea = milkTea;
    }

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

    @Override
    public double cost() {
        return milkTea.cost();
    }
}

4. 具体装饰:加珍珠
public class PearlDecorator extends MilkTeaDecorator {
    public PearlDecorator(MilkTea milkTea) {
        super(milkTea);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", 珍珠";
    }

    @Override
    public double cost() {
        return super.cost() + 2.0; // 珍珠加价
    }
}

5. 具体装饰:加布丁
public class PuddingDecorator extends MilkTeaDecorator {
    public PuddingDecorator(MilkTea milkTea) {
        super(milkTea);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", 布丁";
    }

    @Override
    public double cost() {
        return super.cost() + 1.0; // 布丁加价
    }
}

6. 使用示例
public class Main {
    public static void main(String[] args) {
        MilkTea baseTea = new SimpleMilkTea();
        System.out.println(baseTea.getDescription() + " => ¥" + baseTea.cost());

        // 加珍珠
        MilkTea pearlTea = new PearlDecorator(baseTea);
        System.out.println(pearlTea.getDescription() + " => ¥" + pearlTea.cost());

        // 再加布丁
        MilkTea pearlPuddingTea = new PuddingDecorator(pearlTea);
        System.out.println(pearlPuddingTea.getDescription() + " => ¥" + pearlPuddingTea.cost());
    }
}


输出:

原味奶茶 => ¥8.0
原味奶茶, 珍珠 => ¥10.0
原味奶茶, 珍珠, 布丁 => $11.0

小结

SimpleMilkTea → 基础奶茶

PearlDecorator、PuddingDecorator → 装饰器,可以自由叠加

组合灵活,避免写出大量继承类(如 珍珠布丁奶茶、双珍珠奶茶)


四、装饰模式的特点

优点:

  • 灵活:运行时可动态组合功能,而非编译时固定继承。

  • 遵循开闭原则(OCP):不修改原有类,就能增强功能。

  • 可无限层叠组合,比如「奶茶 + 双份珍珠 + 双份布丁」。

缺点:

  • 层数过多时,调试、排查比较麻烦。

  • 对象包装链过长时,可能影响性能。


五、实际应用场景: Java IO 库


InputStreamBufferedInputStreamDataInputStream 就是典型的装饰模式。每一层包装为原始流提供新功能。

1. 结构回顾:装饰模式骨架

  • Component(抽象组件)InputStream 抽象类

  • ConcreteComponent(具体组件)FileInputStreamByteArrayInputStream

  • Decorator(抽象装饰类)FilterInputStream

  • ConcreteDecorator(具体装饰类)BufferedInputStreamDataInputStreamPushbackInputStream


2. 源码入口:InputStream

public abstract class InputStream implements Closeable {
    public abstract int read() throws IOException;
    // 还有一些 read(byte[])、skip() 等默认实现
}

这里定义了数据读取的抽象接口,所有输入流都得实现。


3. 被装饰的具体组件:FileInputStream

public class FileInputStream extends InputStream {
    private final FileDescriptor fd;
    
    @Override
    public int read() throws IOException {
        return read0();
    }

    private native int read0() throws IOException;
}

这是最基础的流,直接从文件里读取字节。
功能很“原始”,没有缓冲、没有数据类型转换。


4. 装饰抽象类:FilterInputStream

public class FilterInputStream extends InputStream {
    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    @Override
    public int read() throws IOException {
        return in.read();  // 委托给被装饰对象
    }
}

关键点:

  • 它持有一个 InputStream in

  • 所有方法都是转发调用(即套娃)。

  • 它本身不加功能,只是“抽象层”,为子类装饰器铺路。


5. 具体装饰:BufferedInputStream

public class BufferedInputStream extends FilterInputStream {
    private static int DEFAULT_BUFFER_SIZE = 8192;
    protected volatile byte buf[];
    protected int count;
    protected int pos;

    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    public BufferedInputStream(InputStream in, int size) {
        super(in);
        buf = new byte[size];
    }

    @Override
    public int read() throws IOException {
        if (pos >= count) {
            fill();  // 从底层 InputStream 批量读取
            if (pos >= count) return -1;
        }
        return buf[pos++] & 0xff;  // 从缓冲区读
    }
}

这里的增强逻辑是 “缓冲”

  • 底层 FileInputStream 一次只能读一个字节 → 效率低。

  • BufferedInputStream 会一次性把数据读到内存缓冲区,再一个个返回 → 减少系统调用,大幅提升性能。


6. 另一个具体装饰:DataInputStream

public class DataInputStream extends FilterInputStream implements DataInput {
    public DataInputStream(InputStream in) {
        super(in);
    }

    public final int readInt() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        int ch3 = in.read();
        int ch4 = in.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0)
            throw new EOFException();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4));
    }
}

这里的增强逻辑是 “数据类型解析”

  • 底层 InputStream 只会提供原始字节

  • DataInputStream 能把字节组合成 intlongUTF 字符串 等高级数据类型。


7. 使用示例

InputStream in = new FileInputStream("data.bin");

// 加缓冲
InputStream buffered = new BufferedInputStream(in);

// 再加数据类型解析
DataInputStream dataIn = new DataInputStream(buffered);

int magic = dataIn.readInt();   // 直接读 int
String msg = dataIn.readUTF(); // 直接读 UTF 字符串

调用链路:
FileInputStream → BufferedInputStream → DataInputStream
这就是装饰模式的典型应用:层层包装,动态增强


8. 总结:Java IO 与装饰模式

  1. 继承树避免爆炸:如果要在所有流都支持“缓冲+数据解析”,继承会爆炸(BufferedFileDataInputStream 之类),装饰模式完美解决。

  2. 灵活组合:你可以只用 BufferedInputStream,也可以只用 DataInputStream,也可以两者结合。

  3. 职责分离:每个装饰器只关心自己的增强逻辑,保持单一职责。


六、代理模式 vs 装饰模式

维度代理模式(Proxy)装饰模式(Decorator)
设计意图控制对对象的访问,隔离真实对象动态地给对象添加新功能
客户端视角客户端以为直接在用目标对象,实际经过代理客户端以为直接在用目标对象,实际经过装饰
关注点“能不能访问、如何访问”“功能增强、行为叠加”
典型职责远程代理、虚拟代理、保护代理(如权限检查、延迟加载、远程调用)在不修改类的情况下增强功能(如日志、缓存、加密、IO 缓冲)
实现方式代理对象持有真实对象的引用,并在方法调用时控制调用过程装饰对象持有被装饰对象的引用,并在方法调用前后添加逻辑
行为变化方法结果通常保持一致,只是访问路径受控方法结果通常增强或变化,功能比原来更多
类结构相似度与装饰模式几乎一致与代理模式几乎一致
典型案例Spring AOP(JDK Proxy / CGLIB)、RPC 桩、MyBatis Mapper 动态代理Java IO (BufferedInputStreamDataInputStream)、GUI 组件装饰、日志增强

一句话区分:

  • 代理模式:重点是“拦路虎”——先过我这一关,再去找目标对象。

  • 装饰模式:重点是“打补丁”——原本能做的事还照样能做,但我在周围加了点料。


装饰模式本质是 组合优于继承 的典型实践,用“层层套娃”的方式解决继承树爆炸的问题。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐