装饰器模式:动态增强功能、IO流源码分析、实战:权限增强

装饰器模式,说白了就是“套娃”。你有一个核心对象,然后一层一层给它加功能。就像给手机贴膜、加壳、挂吊坠——手机还是那个手机,但功能(或者说特性)变多了。

我刚开始接触这个模式时,总觉得它和继承很像。后来在项目中踩过坑才明白,这两者完全是两码事。继承是在编译期就定死了,而装饰器是在运行时动态组合。你想想看,如果每个功能组合都搞一个子类,那得有多少类?

模式核心:开闭原则的完美实践

装饰器模式的核心思想就八个字:对扩展开放,对修改关闭。你不需要修改原有代码,就能给对象添加新行为。

它的结构其实很简单:

  • Component:抽象组件,定义核心接口
  • ConcreteComponent:具体组件,实现核心功能
  • Decorator:抽象装饰器,持有Component引用
  • ConcreteDecorator:具体装饰器,增强功能

嗯,这里要注意:装饰器和被装饰对象实现同一个接口。这是关键点,否则没法“套娃”。

核心要点:装饰器模式 vs 继承

对比项装饰器模式继承
扩展方式运行时动态组合编译期静态绑定
类数量线性增长(N个装饰器)指数增长(2^N种组合)
灵活性高,可任意组合低,组合固定
典型场景IO流、权限增强is-a关系明确时

经典案例:Java IO流源码分析

Java IO流是装饰器模式最经典的教科书级实现。我当年看源码时,第一反应是“这设计太优雅了”。

你看 InputStream 这个抽象类:

public abstract class InputStream implements Closeable {
    public abstract int read() throws IOException;
    // 其他方法...
}

然后有 FileInputStream 这种具体组件:

public class FileInputStream extends InputStream {
    public int read() throws IOException {
        // 从文件读取一个字节
    }
}

接着是 FilterInputStream,它就是抽象装饰器:

public class FilterInputStream extends InputStream {
    protected volatile InputStream in;
    
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    
    public int read() throws IOException {
        return in.read();
    }
}

然后 BufferedInputStream 继承 FilterInputStream,添加缓冲功能:

public class BufferedInputStream extends FilterInputStream {
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
    
    public synchronized int read() throws IOException {
        // 先检查缓冲区,有就直接返回
        // 没有则从底层流读取并填充缓冲区
    }
}

使用的时候就是这样:

InputStream in = new BufferedInputStream(
                    new FileInputStream("data.txt"));

你看,FileInputStream 负责从文件读字节,BufferedInputStream 给它加了个缓冲区。如果还需要其他功能,比如数据压缩,再加一层 GZIPInputStream 就行。

我的经验:我曾经接手过一个老项目,里面IO操作全是裸的FileInputStream,没有缓冲。结果读取大文件时性能惨不忍睹。加一层BufferedInputStream,性能提升了10倍不止。这就是装饰器的威力——不改原有代码,轻松增强。

SVG:装饰器模式结构图

<<interface>> Component ConcreteComponent Decorator - component: Component ConcreteDecoratorA 被装饰的Component 装饰器持有Component引用,可以递归调用 每个装饰器增强一部分功能

实战:权限增强系统

光说不练假把式。咱们来写一个权限增强的实战案例。

假设你有一个订单服务,核心功能是查询订单。现在需要加权限控制:普通用户只能看自己的订单,管理员可以看所有订单。

先定义接口:

public interface OrderService {
    List<Order> getOrders(Long userId);
}

核心实现:

public class OrderServiceImpl implements OrderService {
    public List<Order> getOrders(Long userId) {
        // 从数据库查询订单
        return orderRepository.findByUserId(userId);
    }
}

抽象装饰器:

public abstract class OrderServiceDecorator implements OrderService {
    protected OrderService target;
    
    public OrderServiceDecorator(OrderService target) {
        this.target = target;
    }
    
    public List<Order> getOrders(Long userId) {
        return target.getOrders(userId);
    }
}

权限增强装饰器:

public class PermissionDecorator extends OrderServiceDecorator {
    private UserContext userContext;
    
    public PermissionDecorator(OrderService target, UserContext userContext) {
        super(target);
        this.userContext = userContext;
    }
    
    public List<Order> getOrders(Long userId) {
        // 权限校验
        User currentUser = userContext.getCurrentUser();
        
        if (currentUser.isAdmin()) {
            // 管理员:可以查任何用户的订单
            return target.getOrders(userId);
        } else if (currentUser.getId().equals(userId)) {
            // 普通用户:只能查自己的
            return target.getOrders(userId);
        } else {
            throw new PermissionException("无权查看其他用户的订单");
        }
    }
}

使用方式:

OrderService orderService = new OrderServiceImpl();
OrderService permissionService = new PermissionDecorator(
    orderService, 
    userContext
);

// 调用时自动进行权限校验
List<Order> orders = permissionService.getOrders(1001L);

避坑指南:我曾经在项目中遇到一个问题——装饰器嵌套顺序搞反了。比如先加缓存装饰器,再加权限装饰器。结果缓存了未授权的数据,导致权限绕过。正确的做法是:权限装饰器在最外层,缓存装饰器在里层。这样每次请求都会先校验权限,再走缓存。

装饰器模式的优缺点

优点缺点
  • 比继承更灵活,运行时动态组合
  • 符合开闭原则,不修改原有代码
  • 可以组合出多种功能
  • 每个装饰器职责单一
  • 多层装饰增加调试难度
  • 容易产生大量小类
  • 装饰器顺序可能影响结果
  • 类型信息丢失(无法直接访问被装饰对象的特有方法)

什么时候用装饰器模式?

我个人习惯在以下场景使用:

  • 需要给对象动态添加功能,且功能可以组合
  • 功能扩展点很多,用继承会导致类爆炸
  • 不想修改原有代码,但又需要增强行为
  • 功能之间有明确的层次关系(比如先校验、再缓存、再日志)

举个例子,我做过一个支付系统。核心是支付接口,然后通过装饰器依次加了:参数校验、风控检查、日志记录、重试机制。每个装饰器只做一件事,组合起来就是完整的支付流程。后来要加新功能,比如限流,只需要再写一个限流装饰器,插到合适位置就行。不改任何已有代码。

小技巧:如果你发现装饰器嵌套太深,代码可读性变差,可以考虑用建造者模式来组装装饰器链。或者用Java 8的Function接口,用链式调用替代多层嵌套。

装饰器模式就是这么个东西——简单,但极其强大。它让你在不破坏现有代码的前提下,灵活地给对象添加各种能力。Java IO流用它,Spring的AOP底层也借鉴了它的思想。理解透了这个模式,你写代码的格局会打开很多。