好记忆不如烂笔头,能记下点东西,就记下点,有时间拿出来看看,也会发觉不一样的感受.
装饰模式是java中很常见的设计模式,如下将好好讲解下java装饰模式相关的知识。本文还是将从:它的前世今生;使用场景;注意事项;代码样例等四个方面,详细阐述下装饰模式的细节内容。
1. 前世今生:从“继承爆炸”到“组合优于继承”
1.1 早期痛点:子类膨胀
在面向对象设计的早期,为了“增强”一个类的功能,最常见的做法就是继承。
例如:
class FileInputStream { … }
class BufferedFileInputStream extends FileInputStream { … }
class GzipFileInputStream extends FileInputStream { … }
class BufferedGzipFileInputStream extends GzipFileInputStream { … }
每增加一种可选能力(缓冲、压缩、加密、校验……),子类数量就会指数级增长,出现所谓的 “子类爆炸”。
1.2 GoF 的救赎:设计模式诞生
1994 年,《Design Patterns: Elements of Reusable Object-Oriented Software》(GoF 书)提出 装饰器模式。
核心思想:
“Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.”
以组合的方式,动态地给对象添加职责,而不是通过继承。
1.3 Java 世界的落地
-
JDK1.0 时代:
java.io包中的FilterInputStream / FilterOutputStream就是装饰器模式的教科书级实现。 -
Java 8 以后:Lambda + 函数式接口让“装饰”粒度更细(如
Function<T,R>链式组合)。 -
现代框架:Spring、MyBatis、Netty 等大量利用装饰器做 可插拔增强(缓存、日志、监控、限流)。
2. 使用场景:何时该用装饰器?
| 业务特征 | 经典示例 | 关键词 |
|---|---|---|
| 功能可叠加、可选 | 给咖啡加配料(牛奶、糖、摩卡) | “可选增强” |
| 运行时动态组合 | Servlet Filter 链 | “动态装配” |
| 避免子类膨胀 | Java IO 流套娃 | “组合优于继承” |
| 不修改老代码 | 给老接口加日志、权限、缓存 | “开闭原则” |
| 横切关注点 | Spring Cache、MyBatis Plugin | “AOP 思想” |
一句话总结:
当“增强”是 横向、可叠加、可选 的,且不想修改既有类时,装饰器是首选。
3. 使用注意事项:避坑指南
| 注意点 | 说明 | 解决策略 |
|---|---|---|
| 接口一致性 | 装饰器与组件必须实现同一抽象 | 定义公共接口/抽象类 |
| 装饰顺序敏感 | 先缓冲后压缩 vs 先压缩后缓冲效果不同 | 文档化顺序;提供 Builder |
| 语义暴露 | 不要暴露具体装饰器类型给客户端 | 面向抽象编程 |
| 过度设计 | 只有两层继承就别硬上装饰器 | 评估复杂度 |
| equals/hashCode | 多层包装后可能破坏相等性 | 正确委托或禁用 |
| 性能损耗 | 过多小对象和间接调用 | 量测 + 对象池/内联 |
4. 代码样例:咖啡计价系统
4.1 需求
-
基础饮品:浓缩咖啡 Espresso(¥15)、综合咖啡 HouseBlend(¥10)
-
可选配料:牛奶 Milk(+¥2)、摩卡 Mocha(+¥3)、豆浆 Soy(+¥1.5)
-
配料可任意叠加,需要动态计算价格与描述
4.2 类图
┌────────────────┐ ┌─────────────────┐
│ Beverage │<─────┤ CondimentDecorator
└────────────────┘ └─────────────────┘
▲ ▲
│ │
┌───────┴───────┐ ┌──────┴────────┐
│ Espresso │ │ Milk │
│ HouseBlend │ │ Mocha │
└───────────────┘ │ Soy │
└───────────────┘
4.3 代码实现
4.3.1 公共抽象
public abstract class Beverage {
protected String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
4.3.2 具体饮品
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
@Override
public double cost() {
return 15;
}
}
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "HouseBlend";
}
@Override
public double cost() {
return 10;
}
}
4.3.3 装饰器基类
public abstract class CondimentDecorator extends Beverage {
// 关键:组合而非继承
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
// 默认实现:子类只需关心自身附加部分
@Override
public String getDescription() {
return beverage.getDescription();
}
}
4.3.4 具体装饰器
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
@Override
public double cost() {
return beverage.cost() + 2;
}
}
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
@Override
public double cost() {
return beverage.cost() + 3;
}
}
public class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
@Override
public double cost() {
return beverage.cost() + 1.5;
}
}
4.3.5 客户端调用
public class CoffeeShop {
public static void main(String[] args) {
// 点一杯 Espresso,加双份 Mocha + 一份 Milk
Beverage order = new Espresso();
order = new Mocha(order); // 第一次包裹
order = new Mocha(order); // 第二次包裹
order = new Milk(order); // 第三次包裹
System.out.println(order.getDescription() + " ¥" + order.cost());
// 输出:Espresso, Mocha, Mocha, Milk ¥23.0
}
}
4.4 与 Java IO 的对比
InputStream in = new FileInputStream("demo.txt");
in = new BufferedInputStream(in); // 缓冲装饰
in = new GZIPInputStream(in); // 压缩装饰
5. 小结思维导图

装饰器模式不仅是一种设计技巧,更是一种 “组合优于继承” 的思维范式。
当你下一次面对“功能叠加”时,想起咖啡的层层配料:
“套娃”不是 bug,而是优雅。
4397



被折叠的 条评论
为什么被折叠?



