装饰器模式:动态增强功能、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:装饰器模式结构图
实战:权限增强系统
光说不练假把式。咱们来写一个权限增强的实战案例。
假设你有一个订单服务,核心功能是查询订单。现在需要加权限控制:普通用户只能看自己的订单,管理员可以看所有订单。
先定义接口:
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底层也借鉴了它的思想。理解透了这个模式,你写代码的格局会打开很多。