装饰器模式:给代码穿上“可脱卸”的外衣

说实话,我最早接触装饰器模式,是在读Java IO源码的时候。当时看到new BufferedInputStream(new FileInputStream("test.txt"))这种写法,第一反应是——这代码写得也太绕了吧?后来真正理解了装饰器,才明白这种“套娃”式的设计有多优雅。

装饰器模式,说白了就是:在不修改原有类的情况下,动态地给对象添加新功能。你想想看,如果每次想加个缓存、加个加密,都要去改原来的类,那代码迟早变成一团乱麻。

为什么需要装饰器?

我遇到过这样一个场景:一个消息推送服务,最开始只支持短信发送。后来产品经理说,要加邮件通知。再后来,又要加微信通知、加日志记录、加失败重试……

如果用继承来解决,你会得到这样的类结构:

// 继承方式的噩梦
class SmsSender {}
class EmailSender extends SmsSender {}
class WechatSender extends SmsSender {}
class SmsWithLogSender extends SmsSender {}
class EmailWithLogSender extends EmailSender {}
// ... 组合爆炸!

每增加一个功能,类的数量就翻倍。这就是典型的“类爆炸”问题。装饰器模式就是专门解决这个痛点的。

装饰器模式的核心结构

先看一张图,理解装饰器的骨架:

装饰器模式结构图 Component(接口) + operation() ConcreteComponent 具体组件(基础功能) Decorator(抽象装饰器) 持有 Component 引用 ConcreteDecoratorA 增强功能A(如加密) ConcreteDecoratorB 增强功能B(如压缩) ConcreteDecoratorC 增强功能C(如缓存) 持有 装饰器与具体组件实现同一接口,装饰器内部持有组件引用 多个装饰器可以层层嵌套,自由组合功能

这个模式就四个角色:

  • Component:抽象接口,定义核心行为
  • ConcreteComponent:具体实现,提供基础功能
  • Decorator:抽象装饰器,持有Component引用
  • ConcreteDecorator:具体装饰器,添加额外功能

Java实现:消息推送的装饰器

回到我前面说的消息推送场景。用装饰器模式,代码会清爽很多:

// 1. 组件接口
interface Notifier {
    void send(String message);
}

// 2. 基础实现
class SmsNotifier implements Notifier {
    @Override
    public void send(String message) {
        System.out.println("发送短信: " + message);
    }
}

// 3. 抽象装饰器
abstract class NotifierDecorator implements Notifier {
    protected Notifier wrapped;

    public NotifierDecorator(Notifier notifier) {
        this.wrapped = notifier;
    }

    @Override
    public void send(String message) {
        wrapped.send(message);
    }
}

// 4. 具体装饰器 - 加日志
class LogDecorator extends NotifierDecorator {
    public LogDecorator(Notifier notifier) {
        super(notifier);
    }

    @Override
    public void send(String message) {
        System.out.println("[日志] 开始发送消息");
        super.send(message);
        System.out.println("[日志] 发送完成");
    }
}

// 5. 具体装饰器 - 加加密
class EncryptDecorator extends NotifierDecorator {
    public EncryptDecorator(Notifier notifier) {
        super(notifier);
    }

    @Override
    public void send(String message) {
        String encrypted = encrypt(message);
        super.send(encrypted);
    }

    private String encrypt(String msg) {
        return "加密{" + msg + "}";
    }
}

// 6. 使用
public class Demo {
    public static void main(String[] args) {
        Notifier notifier = new SmsNotifier();
        notifier = new LogDecorator(notifier);
        notifier = new EncryptDecorator(notifier);

        notifier.send("你好,世界");
        // 输出:
        // [日志] 开始发送消息
        // 发送短信: 加密{你好,世界}
        // [日志] 发送完成
    }
}

你看,想加什么功能,就包一层装饰器。顺序也可以自由调整。这就是装饰器的精髓——运行时动态组合

C++实现:同样的模式,不同的风味

C++的实现思路完全一样,但要注意内存管理。我个人习惯用智能指针来避免内存泄漏:

#include <iostream>
#include <memory>
#include <string>

// 组件接口
class Notifier {
public:
    virtual ~Notifier() = default;
    virtual void send(const std::string& message) = 0;
};

// 基础实现
class SmsNotifier : public Notifier {
public:
    void send(const std::string& message) override {
        std::cout << "发送短信: " << message << std::endl;
    }
};

// 抽象装饰器
class NotifierDecorator : public Notifier {
protected:
    std::shared_ptr<Notifier> wrapped;
public:
    NotifierDecorator(std::shared_ptr<Notifier> notifier)
        : wrapped(notifier) {}
    void send(const std::string& message) override {
        wrapped->send(message);
    }
};

// 具体装饰器 - 日志
class LogDecorator : public NotifierDecorator {
public:
    using NotifierDecorator::NotifierDecorator;
    void send(const std::string& message) override {
        std::cout << "[日志] 开始发送消息" << std::endl;
        NotifierDecorator::send(message);
        std::cout << "[日志] 发送完成" << std::endl;
    }
};

// 具体装饰器 - 加密
class EncryptDecorator : public NotifierDecorator {
public:
    using NotifierDecorator::NotifierDecorator;
    void send(const std::string& message) override {
        std::string encrypted = "{加密}" + message;
        NotifierDecorator::send(encrypted);
    }
};

int main() {
    auto notifier = std::make_shared<SmsNotifier>();
    auto logDecorator = std::make_shared<LogDecorator>(notifier);
    auto encryptDecorator = std::make_shared<EncryptDecorator>(logDecorator);

    encryptDecorator->send("你好,世界");
    return 0;
}
小提示:C++中如果不用智能指针,一定要在析构函数中正确释放资源。我曾经在项目里因为忘记delete装饰链上的对象,导致内存泄漏排查了一整天。

Java IO流中的装饰器——最经典的案例

Java的IO库是装饰器模式最典型的应用。你看这个:

// 基础:从文件读取字节
FileInputStream fis = new FileInputStream("data.txt");

// 装饰:加缓冲
BufferedInputStream bis = new BufferedInputStream(fis);

// 再装饰:加解压缩
GZIPInputStream gzis = new GZIPInputStream(bis);

// 再装饰:加对象反序列化
ObjectInputStream ois = new ObjectInputStream(gzis);

// 最终:读取一个对象
Object obj = ois.readObject();

每一层装饰器只做一件事,组合起来就能完成复杂功能。这就是组合优于继承的绝佳证明。

装饰器类 功能 装饰对象
BufferedInputStream 提供缓冲,减少磁盘IO InputStream
DataInputStream 读取基本数据类型 InputStream
GZIPInputStream 解压GZIP格式数据 InputStream
ObjectInputStream 反序列化Java对象 InputStream
LineNumberReader 读取文本并记录行号 Reader

Java与C++实现对比

对比维度 Java C++
接口定义 interface 关键字 纯虚类(virtual ... = 0)
内存管理 自动GC,无需操心 智能指针或手动管理
装饰器持有方式 直接持有接口引用 通常用 shared_ptr
典型应用 java.io 包、Servlet Filter 标准库 stream、boost
代码量 相对简洁 略多(需要处理构造/析构)
注意:装饰器模式虽然灵活,但不要滥用。如果装饰器嵌套超过3-4层,代码的可读性会急剧下降。我见过有人把装饰器套了7层,调试的时候简直想骂人。

避坑指南

我曾经在一个项目中,用装饰器给每个请求都加了权限校验、日志记录、性能统计、参数校验……结果发现请求延迟增加了30%。后来才意识到,装饰器虽然方便,但每一层都有性能开销。对于高频调用的场景,要慎重使用多层装饰。

另外,装饰器模式有一个“小陷阱”:装饰器与具体组件的类型不同。你不能把装饰后的对象当作具体组件来用。比如:

// 这样不行
SmsNotifier sn = new LogDecorator(new SmsNotifier());

// 必须这样
Notifier n = new LogDecorator(new SmsNotifier());

如果你需要用到具体组件的特有方法,装饰器模式就不太合适了。这时候可以考虑适配器模式或者代理模式。

什么时候用装饰器?

  • 需要给对象动态添加功能,且功能可以自由组合
  • 不想通过继承导致类爆炸
  • 功能需要运行时决定,而不是编译时
  • 功能之间相互独立,没有依赖关系

装饰器模式,说白了就是“用组合代替继承,用包装代替修改”。它让代码更灵活,但也要求你更克制。记住:好的设计模式,是让代码更容易理解和维护,而不是为了炫技。

核心要点回顾:

  • 装饰器模式动态扩展功能,不修改原有代码
  • 核心是组合而非继承,避免类爆炸
  • Java IO 是教科书级的应用案例
  • C++ 实现注意智能指针管理生命周期
  • 嵌套层数不宜过多,注意性能开销

公众号:蓝海资料掘金营,微信deep3321