装饰器模式:定义、结构、实现、与继承的区别、优缺点、应用场景

装饰器模式,说实话,是我个人非常喜欢的一个模式。为什么?因为它太实用了。你想想看,我们写代码的时候,经常遇到一个场景:要给一个已有的对象增加新的功能,但又不想修改它的代码。这时候,装饰器模式就派上用场了。

我记得有一次,我在做一个日志系统。核心的日志记录器已经写好了,但客户突然要求给日志加上时间戳、线程ID、甚至加密功能。如果改核心代码,那风险太大了。后来我用装饰器模式,一层层包装,完美解决了问题。

什么是装饰器模式?

说白了,装饰器模式就是动态地给一个对象添加一些额外的职责。就跟你给手机套个壳、贴个膜一样——手机还是那个手机,但功能(保护、美观)增加了。

从定义上看,装饰器模式属于结构型模式。它提供了一种比继承更灵活的扩展方式,无需修改原有代码,就能给对象添加新功能。

模式结构

装饰器模式的结构其实不复杂,我画个图你就明白了。

Component + operation() ConcreteComponent + operation() // 核心业务逻辑 Decorator - component: Component + operation() ConcreteDecoratorA + operation() // 调用父类 + 新增功能A ConcreteDecoratorB + operation() // 调用父类 + 新增功能B 聚合

结构中的几个角色,我简单说一下:

  • Component:抽象组件,定义了对象的接口。可以是接口或抽象类。
  • ConcreteComponent:具体组件,实现了核心业务逻辑。这是我们要装饰的对象。
  • Decorator:抽象装饰类,持有Component的引用,并实现其接口。
  • ConcreteDecorator:具体装饰类,给组件添加新的功能。

代码实现

光说不练假把式。咱们直接上代码,用Java实现一个简单的咖啡订单系统。嗯,这里要注意,咖啡本身是核心,加料就是装饰。

// 1. 抽象组件
public interface Coffee {
    double cost();
    String description();
}

// 2. 具体组件
public class Espresso implements Coffee {
    @Override
    public double cost() {
        return 5.0;
    }
    
    @Override
    public String description() {
        return "浓缩咖啡";
    }
}

// 3. 抽象装饰类
public abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
    
    @Override
    public double cost() {
        return coffee.cost();
    }
    
    @Override
    public String description() {
        return coffee.description();
    }
}

// 4. 具体装饰类 - 加牛奶
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public double cost() {
        return super.cost() + 2.0;
    }
    
    @Override
    public String description() {
        return super.description() + " + 牛奶";
    }
}

// 5. 具体装饰类 - 加糖
public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public double cost() {
        return super.cost() + 1.5;
    }
    
    @Override
    public String description() {
        return super.description() + " + 糖";
    }
}

// 6. 客户端使用
public class Client {
    public static void main(String[] args) {
        Coffee coffee = new Espresso();
        System.out.println(coffee.description() + " 价格: " + coffee.cost());
        
        // 加牛奶
        coffee = new MilkDecorator(coffee);
        // 再加糖
        coffee = new SugarDecorator(coffee);
        
        System.out.println(coffee.description() + " 价格: " + coffee.cost());
        // 输出: 浓缩咖啡 + 牛奶 + 糖 价格: 8.5
    }
}
💡 我的小技巧:在实际项目中,我习惯把装饰器的构造方法写成链式调用风格。这样客户端代码看起来更清爽,就像 new SugarDecorator(new MilkDecorator(new Espresso()))

装饰器 vs 继承

很多人会问:我直接用继承不也能扩展功能吗?为什么非要用装饰器?

这个问题问得好。我举个例子你就明白了。

对比维度 继承 装饰器
扩展方式 静态编译期决定 动态运行时组合
灵活性 低,子类数量爆炸 高,自由组合
职责单一 子类承担多种职责 每个装饰类只做一件事
对原有代码影响 需要修改父类 完全不需要修改
适用场景 明确稳定的层次结构 需要动态添加功能

你想想看,如果咖啡有10种配料,用继承你得写多少子类?2的10次方,1024个!而用装饰器,只需要10个装饰类,自由组合就行。

⚠️ 我曾经踩过的坑:有一次我过度使用装饰器,给一个对象套了七八层。结果调试的时候,调用栈深得吓人,排查问题花了整整一天。后来我给自己定了个规矩:装饰层数不超过3层,超过就考虑重构。

优缺点分析

任何模式都不是银弹,装饰器也一样。

优点

  • 开闭原则:对扩展开放,对修改关闭。不用改原有代码就能加功能。
  • 灵活组合:运行时动态组合,想加就加,想减就减。
  • 职责单一:每个装饰类只负责一个额外功能,符合单一职责原则。
  • 避免类爆炸:比继承的类数量少得多。

缺点

  • 增加复杂度:多了很多小类,新手看了容易懵。
  • 调试困难:多层包装后,调用链变长,排查问题费劲。
  • 类型问题:如果客户端依赖具体类型,装饰器会破坏类型信息。

应用场景

我个人在实际项目中,主要在以下几个场景用装饰器模式:

  1. IO流处理:Java的BufferedInputStream、DataInputStream等,就是典型的装饰器。
  2. 日志增强:给核心业务方法加上日志、性能监控、权限校验等。
  3. 缓存系统:给数据访问层加上缓存装饰,不影响原有查询逻辑。
  4. UI组件:给按钮、文本框等组件添加边框、滚动条、阴影等效果。
  5. 中间件架构:像Express、Koa的中间件机制,本质上就是装饰器思想。

核心要点总结:

  • 装饰器模式的核心是组合优于继承
  • 每个装饰类只做一件事,保持单一职责
  • 装饰层数不宜过多,建议不超过3层
  • 适合需要动态、灵活扩展功能的场景

好了,装饰器模式就讲到这里。说白了,它就是给对象「穿衣服」的艺术。你学会了吗?

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