一、装饰器模式:动态扩展功能
装饰器模式,说白了就是给对象「穿衣服」。
你有一个基础对象,比如一杯咖啡。你想加牛奶、加糖、加奶油。传统做法是什么?继承!但继承有个大问题——类会爆炸。牛奶咖啡、糖咖啡、奶油咖啡、牛奶糖咖啡、牛奶奶油咖啡...你想想看,这得多少个类?
装饰器模式换了个思路:不搞继承,搞组合。把装饰功能一层层包上去,像俄罗斯套娃一样。每个装饰器只负责一件事,组合起来就能产生无限可能。
1.1 模式结构
装饰器模式有四个角色:
- Component(抽象构件):定义对象的接口
- ConcreteComponent(具体构件):被装饰的原始对象
- Decorator(抽象装饰器):持有Component引用,实现相同接口
- ConcreteDecorator(具体装饰器):添加额外功能
我画了一张图,帮你理清关系:
1.2 代码示例:咖啡加料
我直接上代码,你一看就明白:
// 抽象构件
public interface Coffee {
double cost();
String description();
}
// 具体构件
public class Espresso implements Coffee {
public double cost() { return 15.0; }
public String description() { return "浓缩咖啡"; }
}
// 抽象装饰器
public abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
public double cost() { return coffee.cost(); }
public String description() { return coffee.description(); }
}
// 具体装饰器:加牛奶
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) { super(coffee); }
public double cost() { return super.cost() + 3.0; }
public String description() { return super.description() + " + 牛奶"; }
}
// 具体装饰器:加糖
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) { super(coffee); }
public double cost() { return super.cost() + 1.5; }
public String description() { return super.description() + " + 糖"; }
}
// 使用
Coffee coffee = new Espresso();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println(coffee.description() + ":" + coffee.cost() + "元");
// 输出:浓缩咖啡 + 牛奶 + 糖:19.5元
你看,代码很干净。每个装饰器只做一件事,组合起来就是任意搭配。
核心思想:装饰器模式用组合替代继承,避免类爆炸。每个装饰器只添加一个功能,通过层层包装实现功能叠加。
二、IO流中的装饰器模式
Java IO流是装饰器模式最经典的例子。我刚开始学Java时,看到new BufferedInputStream(new FileInputStream("test.txt"))这种写法,觉得好奇怪。为什么要套一层?
后来才明白,这就是装饰器模式啊!
2.1 IO流类层次
InputStream是抽象构件,FileInputStream、ByteArrayInputStream是具体构件。FilterInputStream是抽象装饰器,BufferedInputStream、DataInputStream、PushbackInputStream是具体装饰器。
我画了张图,帮你理清IO流的装饰器结构:
2.2 为什么IO流要这么设计?
你想想看,文件读取、网络读取、字节数组读取,它们的基础操作不一样。但缓冲、数据转换、回退这些功能,是通用的。
如果不用装饰器模式,每个InputStream子类都要自己实现缓冲功能。那代码得多冗余?
装饰器模式把「基础读取」和「增强功能」解耦了。你想加缓冲就套BufferedInputStream,想读基本类型就套DataInputStream,想回退就套PushbackInputStream。自由组合,互不干扰。
我的经验:我在做文件上传系统时,用装饰器模式给上传流加了进度监控功能。写了个ProgressMonitorInputStream,套在原始流外面,就能实时计算上传百分比。不改原始代码,功能就加上了。
三、实战:给订单系统加功能
我拿一个真实场景来说。订单系统里,计算价格是个核心功能。基础价格算完后,可能要加折扣、加运费、加税费。
传统做法:写一堆if-else。但需求一变,代码就乱成一锅粥。
用装饰器模式就清爽多了:
// 抽象构件
public interface Order {
double getTotal();
String getDescription();
}
// 具体构件:基础订单
public class BaseOrder implements Order {
private double amount;
public BaseOrder(double amount) { this.amount = amount; }
public double getTotal() { return amount; }
public String getDescription() { return "订单金额:" + amount; }
}
// 抽象装饰器
public abstract class OrderDecorator implements Order {
protected Order order;
public OrderDecorator(Order order) { this.order = order; }
public double getTotal() { return order.getTotal(); }
public String getDescription() { return order.getDescription(); }
}
// 具体装饰器:折扣
public class DiscountDecorator extends OrderDecorator {
private double discountRate;
public DiscountDecorator(Order order, double rate) {
super(order);
this.discountRate = rate;
}
public double getTotal() {
return super.getTotal() * (1 - discountRate);
}
public String getDescription() {
return super.getDescription() + ",打" + (int)(discountRate * 100) + "折";
}
}
// 具体装饰器:运费
public class ShippingDecorator extends OrderDecorator {
private double shippingFee;
public ShippingDecorator(Order order, double fee) {
super(order);
this.shippingFee = fee;
}
public double getTotal() {
return super.getTotal() + shippingFee;
}
public String getDescription() {
return super.getDescription() + ",运费:" + shippingFee;
}
}
// 使用
Order order = new BaseOrder(100);
order = new DiscountDecorator(order, 0.2); // 打8折
order = new ShippingDecorator(order, 10); // 加10元运费
System.out.println(order.getDescription());
System.out.println("最终价格:" + order.getTotal());
// 输出:订单金额:100.0,打20折,运费:10.0
// 最终价格:90.0
注意:装饰器模式不是万能的。如果装饰器太多层,调试时会很痛苦。我曾经遇到过一个被套了5层装饰器的对象,出问题时根本不知道哪一层出了问题。建议装饰器不超过3层,超过时考虑用策略模式或责任链模式。
四、装饰器 vs 继承 vs 代理
我整理了个对比表,帮你快速区分:
| 对比维度 | 装饰器模式 | 继承 | 代理模式 |
|---|---|---|---|
| 扩展方式 | 组合,动态包装 | 静态继承 | 控制访问 |
| 功能关系 | 增强原有功能 | 覆盖或新增 | 控制或延迟 |
| 对象个数 | 多个装饰器可叠加 | 单一子类 | 单一代理 |
| 典型场景 | IO流、UI组件 | 类层次扩展 | 懒加载、权限控制 |
说白了,继承是「我是谁」的关系,装饰器是「我有什么」的关系。代理则是「谁在替我干活」的关系。
一句话总结:装饰器模式让你在不修改原有代码的前提下,给对象动态添加新功能。它用组合替代继承,灵活且可叠加。Java IO流就是最好的教科书。
嗯,装饰器模式就讲到这里。记住它的核心:一层层包装,一点点增强。下次你看到new BufferedInputStream(new FileInputStream(...)),就知道这是装饰器模式在干活了。