1. 装饰器模式初探:从咖啡店点单说起
第一次接触装饰器模式是在2015年参与一个电商促销系统开发时。当时需要动态地为商品添加各种促销标签(满减、折扣、赠品等),而传统的继承方式已经让代码臃肿不堪。直到团队架构师在白板上画出那个经典的咖啡店示意图,我才恍然大悟——这不正是星巴克的实际业务场景吗?
想象你走进一家咖啡店:
- 基础饮品:浓缩咖啡(Espresso)5元
- 可选配料:牛奶+2元,糖浆+1元,奶油+1.5元
如果采用继承方式实现,我们需要创建无数子类:
- EspressoWithMilk
- EspressoWithSugar
- EspressoWithMilkAndSugar
- ...(组合爆炸)
而装饰器模式的精妙之处在于,它用"包装"代替"继承"。就像现实中的咖啡杯,我们可以在原始咖啡外不断叠加新的"装饰层",每个装饰层都能改变最终的价格和描述,却不会影响内部的咖啡本质。
java复制// 基础组件接口
interface Coffee {
double getCost();
String getDescription();
}
// 具体组件
class Espresso implements Coffee {
public double getCost() { return 5.0; }
public String getDescription() { return "Espresso"; }
}
// 装饰器抽象类
abstract class CoffeeDecorator implements Coffee {
protected final Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
}
// 具体装饰器
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public double getCost() {
return decoratedCoffee.getCost() + 2.0;
}
public String getDescription() {
return decoratedCoffee.getDescription() + ", Milk";
}
}
这个简单的例子揭示了装饰器模式的三大特征:
- 透明性:装饰后的对象依然遵循原始接口
- 动态组合:运行时自由添加/移除功能
- 避免继承爆炸:通过组合实现灵活扩展
关键认知:装饰器模式不是简单的"包装纸",而是保留了原始对象所有行为的"智能包装"——它既能添加新功能,又会将所有未修改的操作委托给被装饰对象。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模式结构深度拆解:UML与角色分析
让我们通过标准UML图来解析装饰器模式的完整结构。下图展示了模式中的四个核心角色及其关系:
code复制+----------------+ +-------------------+
| Component | | Decorator |
+----------------+ +-------------------+
| + operation() |<------| - component: Component |
+----------------+ +-------------------+
^ ^
| |
+----------------+ +-------------------+
| ConcreteComponent| | ConcreteDecoratorA |
+----------------+ +-------------------+
| + operation() | | + operation() |
+----------------+ | + addedBehavior() |
+-------------------+
2.1 核心角色职责
Component(抽象组件)
- 定义原始对象和装饰器对象的共同接口
- 可以是接口或抽象类
- 示例:前文的Coffee接口
ConcreteComponent(具体组件)
- 实现Component的基本功能
- 即将被装饰的"裸对象"
- 示例:Espresso类
Decorator(抽象装饰器)
- 持有Component引用(通过组合)
- 实现Component接口(与具体组件保持一致性)
- 示例:CoffeeDecorator抽象类
ConcreteDecorator(具体装饰器)
- 添加新的职责或行为
- 可以调用父类方法实现功能叠加
- 示例:MilkDecorator类
2.2 方法调用链解析
当客户端调用装饰对象的operation()方法时,实际发生了怎样的调用过程?以getDescription()为例:
-
创建基础组件:
Coffee coffee = new Espresso()- 此时调用coffee.getDescription() → "Espresso"
-
添加牛奶装饰:
coffee = new MilkDecorator(coffee)- 现在调用链变为:
MilkDecorator.getDescription()
→ super.decoratedCoffee.getDescription() + ", Milk"
→ "Espresso, Milk"
- 现在调用链变为:
-
继续添加糖浆装饰:
coffee = new SyrupDecorator(coffee)- 调用链变为:
SyrupDecorator.getDescription()
→ super.decoratedCoffee.getDescription() + ", Syrup"
→ "Espresso, Milk, Syrup"
- 调用链变为:
这种链式调用机制使得功能可以无限叠加,而每个装饰器只需关注自己新增的部分。在JDK的IO类库中,BufferedReader(FileReader)正是这种思想的经典实现。
3. 实战应用:Java I/O中的装饰器模式
Java的I/O系统是装饰器模式最著名的应用场景之一。让我们解剖这个典型案例:
java复制InputStream in = new FileInputStream("test.txt");
InputStream bin = new BufferedInputStream(in);
InputStream gzin = new GZIPInputStream(bin);
DataInputStream din = new DataInputStream(gzin);
这段代码展示了四层装饰:
- FileInputStream:具体组件,提供基础文件读取功能
- BufferedInputStream:添加缓冲功能的具体装饰器
- GZIPInputStream:添加解压缩功能的具体装饰器
- DataInputStream:添加基本数据类型读取功能的具体装饰器
3.1 JDK实现精妙之处
-
灵活的组装方式
- 可以任意组合装饰器,如仅缓冲:
new BufferedInputStream(new FileInputStream(...)) - 或缓冲+解压:
new GZIPInputStream(new BufferedInputStream(...))
- 可以任意组合装饰器,如仅缓冲:
-
透明的接口一致性
- 所有装饰器都继承自InputStream抽象类
- 客户端无需关心具体装饰层次
-
动态的功能扩展
- 新增装饰器不影响现有代码(开闭原则)
- 如JDK1.8新增的加密流可以无缝集成
陷阱警示:关闭装饰流时,应该只关闭最外层的装饰器,它会自动委托关闭内部流。如果手动关闭每个装饰器,可能导致重复关闭底层资源引发异常。
3.2 自定义装饰器示例
我们可以模仿JDK实现自己的装饰器。比如创建一个将输出全部转为大写的装饰器:
java复制public class UpperCaseInputStream extends FilterInputStream {
public UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1) ? c : Character.toUpperCase(c);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for (int i = off; i < off+result; i++) {
b[i] = (byte)Character.toUpperCase((char)b[i]);
}
return result;
}
}
使用时:
java复制InputStream in = new UpperCaseInputStream(
new FileInputStream("test.txt"));
int c;
while ((c = in.read()) != -1) {
System.out.print((char)c); // 输出全大写内容
}
这个例子展示了装饰器模式的强大扩展性——无需修改原有类,就能给IO流添加全新的行为。
4. 模式对比:装饰器vs其他结构型模式
4.1 装饰器 vs 适配器
| 维度 | 装饰器模式 | 适配器模式 |
|---|---|---|
| 目的 | 增强现有功能 | 转换接口兼容 |
| 关系 | 同接口扩展 | 不同接口转换 |
| 调用方式 | 递归委托 | 一次性转换 |
| 典型应用 | Java I/O流 | 旧系统整合 |
关键区别:适配器是"接口转换器",装饰器是"功能增强器"。
4.2 装饰器 vs 代理
| 维度 | 装饰器模式 | 代理模式 |
|---|---|---|
| 关注点 | 功能增强 | 访问控制 |
| 创建时机 | 客户端动态组合 | 通常预先确定 |
| 透明度 | 对客户端透明 | 可能对客户端隐藏 |
| 典型应用 | 动态添加功能 | 延迟加载、权限检查 |
实际项目中,Spring AOP的拦截器实现同时运用了两种模式的思想。
4.3 装饰器 vs 组合
虽然都使用递归组合结构,但:
- 组合模式处理"部分-整体"层次结构
- 装饰模式处理"核心-附加"功能层次
组合模式更关注结构的统一性,装饰模式更关注功能的动态性。
5. 现代框架中的装饰器模式应用
5.1 Spring框架中的应用
Spring中装饰器模式的典型应用是HttpServletRequestWrapper。当需要修改请求参数时,可以创建自定义装饰器:
java复制public class CustomRequestWrapper extends HttpServletRequestWrapper {
private final Map<String, String[]> modifiedParams;
public CustomRequestWrapper(HttpServletRequest request) {
super(request);
modifiedParams = new HashMap<>(request.getParameterMap());
}
public void setParameter(String name, String value) {
modifiedParams.put(name, new String[]{value});
}
@Override
public String getParameter(String name) {
String[] values = modifiedParams.get(name);
return values != null ? values[0] : null;
}
@Override
public Map<String, String[]> getParameterMap() {
return Collections.unmodifiableMap(modifiedParams);
}
}
在过滤器中应用:
java复制public class CustomFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
CustomRequestWrapper wrappedRequest = new CustomRequestWrapper(
(HttpServletRequest)request);
wrappedRequest.setParameter("newParam", "value");
chain.doFilter(wrappedRequest, response);
}
}
5.2 MyBatis的缓存装饰器
MyBatis的缓存模块采用多层装饰器结构:
- 基本缓存实现:PerpetualCache
- 装饰器包括:
- LruCache(LRU淘汰)
- FifoCache(FIFO淘汰)
- SoftCache(软引用缓存)
- LoggingCache(日志记录)
- SynchronizedCache(线程安全)
- ...(可自由组合)
配置示例:
xml复制<cache eviction="FIFO" flushInterval="60000"
size="512" readOnly="true"/>
这实际上创建了如下装饰链:
SynchronizedCache → LoggingCache → FifoCache → PerpetualCache
5.3 React高阶组件
在前端领域,React的高阶组件(HOC)本质上是装饰器模式的实现。例如:
javascript复制function withLogger(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
console.log(`Component ${WrappedComponent.name} mounted`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
// 使用
const EnhancedComponent = withLogger(MyComponent);
这种模式让开发者可以在不修改原组件的情况下,添加日志、权限校验等横切关注点。
6. 实现装饰器模式的7个关键要点
根据多年实践,我总结了实现装饰器模式的黄金准则:
-
保持接口一致性
- 装饰器必须实现与被装饰对象相同的接口
- 这是透明性的基础保障
-
使用组合而非继承
- 装饰器持有组件实例的引用
- 通过委托实现功能扩展
-
保持装饰器的轻量化
- 每个装饰器只关注单一功能增强
- 避免创建"全能装饰器"
-
注意装饰顺序
- 某些装饰器可能有顺序依赖
- 比如加密应该在压缩之后
-
谨慎处理对象标识
- 装饰后的对象≠原始对象
- 需要重写equals/hashCode时要特别小心
-
控制装饰层数
- 过深的装饰链会影响性能
- 建议监控并设置合理上限
-
明确生命周期责任
- 由最外层装饰器负责资源释放
- 内部装饰器只处理自身资源
7. 性能考量与优化策略
虽然装饰器模式非常灵活,但不当使用会导致性能问题:
7.1 典型性能陷阱
-
调用链过长
- 每个装饰层都会增加方法调用开销
- 实测案例:10层装饰的方法调用比直接调用慢3-5倍
-
重复计算
- 多层装饰器可能重复执行相同计算
- 比如多个缓存装饰器检查相同key
-
内存占用
- 每个装饰器都是独立对象
- 装饰链会保持所有中间对象的引用
7.2 优化方案
-
缓存装饰结果
java复制class CachingDecorator implements Component { private final Component delegate; private Map<String, Object> cache = new HashMap<>(); public Object operation(String param) { return cache.computeIfAbsent(param, k -> delegate.operation(k)); } } -
控制装饰深度
- 设置装饰层数阈值
- 超过阈值时报警或改用其他模式
-
使用轻量级装饰器
- 避免在装饰器中存储大量状态
- 无状态装饰器可考虑共享实例
-
选择性装饰
- 只对热点路径使用装饰器
- 其他路径直接使用基础组件
8. 测试装饰器模式的实用技巧
测试装饰器时需要特别关注以下方面:
8.1 单元测试策略
-
独立测试每个装饰器
java复制@Test public void testMilkDecorator() { Coffee coffee = new Espresso(); coffee = new MilkDecorator(coffee); assertEquals(7.0, coffee.getCost(), 0.01); assertTrue(coffee.getDescription().contains("Milk")); } -
测试装饰器组合
java复制@Test public void testDecoratorChain() { Coffee coffee = new Espresso(); coffee = new MilkDecorator(coffee); coffee = new SugarDecorator(coffee); assertEquals(8.0, coffee.getCost(), 0.01); assertTrue(coffee.getDescription().contains("Milk")); assertTrue(coffee.getDescription().contains("Sugar")); }
8.2 集成测试要点
-
验证装饰顺序影响
- 测试不同装饰顺序是否产生预期结果
-
边界条件测试
- 空装饰器链
- 重复装饰同类型装饰器
- 装饰null对象
-
性能测试
- 监控不同装饰深度下的响应时间
- 建立性能基线
测试经验:使用Mock对象模拟被装饰组件,可以精准测试装饰器自身逻辑,避免依赖具体组件实现。
9. 与其他模式的协同应用
装饰器模式常与其他模式配合使用,产生更强大的效果:
9.1 装饰器+工厂模式
通过工厂封装装饰器的创建过程:
java复制public class CoffeeFactory {
public static Coffee createCoffee(String type) {
Coffee coffee = new Espresso();
if (type.contains("milk")) {
coffee = new MilkDecorator(coffee);
}
if (type.contains("sugar")) {
coffee = new SugarDecorator(coffee);
}
return coffee;
}
}
9.2 装饰器+策略模式
装饰器负责功能增强,策略模式负责算法选择:
java复制interface CompressionStrategy {
byte[] compress(byte[] data);
}
class ZipCompressionStrategy implements CompressionStrategy { ... }
class GzipCompressionStrategy implements CompressionStrategy { ... }
class CompressingOutputStream extends FilterOutputStream {
private final CompressionStrategy strategy;
public CompressingOutputStream(OutputStream out,
CompressionStrategy strategy) {
super(out);
this.strategy = strategy;
}
@Override
public void write(byte[] b) throws IOException {
byte[] compressed = strategy.compress(b);
super.write(compressed);
}
}
9.3 装饰器+观察者模式
装饰器实现增强功能,观察者模式实现事件通知:
java复制class NotifyingInputStream extends FilterInputStream {
private final List<InputStreamListener> listeners = new ArrayList<>();
public void addListener(InputStreamListener l) {
listeners.add(l);
}
@Override
public int read() throws IOException {
int data = super.read();
if (data != -1) {
listeners.forEach(l -> l.onDataRead(data));
}
return data;
}
}
10. 实际项目中的经典误用与修正
在代码评审中,我经常遇到装饰器模式的这些典型误用:
10.1 误用案例1:破坏接口一致性
错误实现:
java复制class BadDecorator {
private Coffee coffee;
// 没有实现Coffee接口!
public void extraMethod() { ... }
}
修正方案:
java复制class GoodDecorator implements Coffee {
private final Coffee coffee;
// 实现所有Coffee方法
public double getCost() {
return coffee.getCost() + 1.0;
}
...
}
10.2 误用案例2:装饰器修改内部状态
错误实现:
java复制class StatefulDecorator implements Coffee {
private Coffee coffee;
private int callCount = 0; // 危险的状态!
public double getCost() {
callCount++;
return coffee.getCost();
}
}
问题:多个装饰器实例共享同一被装饰对象时,状态会互相干扰
修正方案:
java复制class StatelessDecorator implements Coffee {
private final Coffee coffee; // final确保不可变
public double getCost() {
return coffee.getCost() * 0.9; // 仅依赖输入计算
}
}
10.3 误用案例3:过度装饰导致性能问题
错误场景:
java复制InputStream in = new FileInputStream(...);
in = new BufferedInputStream(in);
in = new LoggingInputStream(in);
in = new MetricsInputStream(in);
in = new ValidationInputStream(in);
// ...又添加了5个装饰器
解决方案:
- 使用组合装饰器封装常用装饰组合
- 实现装饰器开关配置
- 对装饰链深度进行监控报警
11. 行业应用案例深度解析
11.1 电商促销系统
某大型电商平台的商品价格计算采用装饰器模式:
- 基础价格 → BasePriceDecorator
- 会员折扣 → MemberDiscountDecorator
- 满减活动 → FullReductionDecorator
- 优惠券 → CouponDecorator
- 运费 → ShippingFeeDecorator
系统支持运行时动态组合:
java复制PriceCalculator calculator = new BasePriceDecorator(product);
if (hasMemberDiscount) {
calculator = new MemberDiscountDecorator(calculator);
}
if (hasCoupon) {
calculator = new CouponDecorator(calculator);
}
// ...
double finalPrice = calculator.calculate();
11.2 游戏装备系统
MMORPG游戏中,角色装备系统完美适用装饰器模式:
- 基础角色 → BasicCharacter
- 武器 → WeaponDecorator
- 护甲 → ArmorDecorator
- 饰品 → AccessoryDecorator
- 增益效果 → BuffDecorator
csharp复制ICharacter hero = new BasicCharacter("战士");
hero = new WeaponDecorator(hero, "圣剑");
hero = new ArmorDecorator(hero, "龙鳞甲");
hero = new BuffDecorator(hero, "狂暴");
Console.WriteLine(hero.GetDescription());
// 输出: 战士[装备:圣剑, 龙鳞甲][增益:狂暴]
11.3 金融风控系统
银行交易风控采用多层装饰器进行校验:
- 基础验证 → BasicValidationDecorator
- 检查字段完整性
- 格式验证 → FormatValidationDecorator
- 验证金额格式等
- 业务规则 → BusinessRuleDecorator
- 检查单笔限额
- 风控模型 → RiskModelDecorator
- 调用风控评分模型
- 黑名单 → BlacklistDecorator
- 检查交易对手黑名单
java复制TransactionValidator validator = new BasicValidationDecorator();
validator = new FormatValidationDecorator(validator);
validator = new BusinessRuleDecorator(validator);
// ...
ValidationResult result = validator.validate(tx);
if (!result.isValid()) {
throw new ValidationException(result.getErrors());
}
12. 各语言实现特色
12.1 Python:使用装饰器语法糖
Python通过@语法原生支持装饰器模式:
python复制def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logger
def say_hello(name):
print(f"Hello, {name}")
# 等效于:
# say_hello = logger(say_hello)
12.2 JavaScript:高阶函数实现
JS利用函数是一等公民的特性:
javascript复制function withLogging(fn) {
return function(...args) {
console.log(`Entering ${fn.name}`);
const result = fn.apply(this, args);
console.log(`Exiting ${fn.name}`);
return result;
};
}
const loggedFetch = withLogging(fetch);
loggedFetch('https://api.example.com');
12.3 Go:通过结构体嵌入
Go语言使用类型嵌入实现类似效果:
go复制type Coffee interface {
Cost() float64
Description() string
}
type Espresso struct{}
func (e Espresso) Cost() float64 { return 5.0 }
func (e Espresso) Description() string { return "Espresso" }
type MilkDecorator struct {
Coffee
}
func (m MilkDecorator) Cost() float64 {
return m.Coffee.Cost() + 2.0
}
func (m MilkDecorator) Description() string {
return m.Coffee.Description() + ", Milk"
}
13. 设计原则与模式关联
13.1 遵循的SOLID原则
-
单一职责原则(SRP)
- 每个装饰器只负责一个明确的功能增强
-
开闭原则(OCP)
- 对扩展开放:可以创建新装饰器
- 对修改关闭:无需修改现有代码
-
依赖倒置原则(DIP)
- 依赖抽象(Component)而非具体实现
13.2 与其他模式的关系
-
与责任链模式
- 都使用链式结构
- 责任链:处理者可能中断处理链
- 装饰器:必定传递请求
-
与组合模式
- 都使用递归组合
- 组合:处理整体-部分关系
- 装饰器:处理核心-增强关系
-
与策略模式
- 都可以改变对象行为
- 策略:替换整个算法
- 装饰器:增强现有行为
14. 演进与变体模式
14.1 透明性 vs 半透明装饰器
标准装饰器保持完全透明,但有时需要"半透明"装饰器:
java复制interface Coffee {
double getCost();
String getDescription();
// 新增专有方法
default boolean hasMilk() { return false; }
}
class MilkDecorator implements Coffee {
// ...
@Override
public boolean hasMilk() { return true; }
}
// 客户端可以检查装饰能力
if (coffee instanceof MilkDecorator) {
// 特殊处理
}
14.2 静态装饰器(编译时)
通过代码生成在编译时实现装饰:
java复制@Decorator
public class LoggingService implements OrderService {
@Inject @Delegate @Any
private OrderService delegate;
public void placeOrder(Order order) {
System.out.println("Placing order: " + order);
delegate.placeOrder(order);
}
}
14.3 动态装饰器(运行时)
利用动态代理实现:
java复制public static <T> T createDecorator(Class<T> interfaceType,
T delegate,
InvocationHandler handler) {
return (T) Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class<?>[] { interfaceType },
(proxy, method, args) -> {
// 前置处理
Object result = handler.invoke(delegate, method, args);
// 后置处理
return result;
});
}
15. 反模式与适用场景判断
15.1 不适合使用装饰器模式的情况
-
接口不稳定
- 组件接口频繁变更会导致所有装饰器需要同步修改
-
需要修改核心行为
- 装饰器应该增强而非改变核心逻辑
-
性能敏感场景
- 深层次的装饰链会影响性能
-
简单扩展需求
- 如果只需要简单扩展,直接继承可能更合适
15.2 替代方案考量
-
策略模式
- 当需要完全替换算法而非增强功能时
-
组合模式
- 处理整体-部分层次结构时
-
模板方法模式
- 在类层次上定义算法骨架时
-
AOP
- 处理横切关注点时(如日志、事务)
16. 复杂度管理策略
当装饰器系统变得复杂时,可以采用以下管理策略:
16.1 装饰器注册表
java复制public class DecoratorRegistry {
private static final Map<Class<?>, List<Class<?>>> registry = new HashMap<>();
static {
register(DataSource.class, LoggingDecorator.class);
register(DataSource.class, MetricsDecorator.class);
// ...
}
public static <T> T decorate(T component) {
List<Class<?>> decorators = registry.get(component.getClass());
if (decorators == null) return component;
T result = component;
for (Class<?> decoratorClass : decorators) {
try {
Constructor<?> ctor = decoratorClass.getConstructor(component.getClass());
result = (T) ctor.newInstance(result);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return result;
}
}
16.2 装饰器工厂
java复制public class DecoratorFactory {
public static Coffee createCoffee(String... decorators) {
Coffee coffee = new Espresso();
for (String decorator : decorators) {
switch (decorator) {
case "milk": coffee = new MilkDecorator(coffee); break;
case "sugar": coffee = new SugarDecorator(coffee); break;
// ...
}
}
return coffee;
}
}
16.3 配置化装饰
通过配置文件定义装饰链:
yaml复制# decorators.yml
chains:
dataSource:
- logging
- metrics
- caching
service:
- validation
- transaction
17. 经典著作中的精辟见解
17.1 《设计模式》GoF原话
"装饰器模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更为灵活。"
关键点:
- 动态添加:运行时而非编译时
- 额外职责:不改变对象核心身份
- 比继承灵活:避免静态继承的局限
17.2 《Head First设计模式》观点
"装饰器模式就像在基础咖啡上添加调料。关键设计原则是:类应该对扩展开放,对修改关闭(开闭原则)。"
强调:
- 符合开闭原则
- 组合优于继承
- 星巴克咖啡是完美类比
17.3 《Clean Code》中的建议
"装饰器模式可以让我们保持类的短小精悍,每个类只做一件事。当我们需要交叉混合各种功能时,装饰器是避免混乱的好方法。"
启示:
- 保持单一职责
- 避免功能膨胀的类
- 清晰的功能组合
18. 常见面试问题解析
18.1 基础理论问题
Q1:装饰器模式与继承的区别?
A1:
- 继承是静态的,在编译时确定;装饰是动态的,在运行时组合
- 继承会导致类爆炸(每个组合一个子类);装饰器使用组合避免此问题
- 继承破坏封装(子类知道父类细节);装饰器只通过接口交互
Q2:装饰器模式的优缺点?
A2:
优点:
- 符合开闭原则
- 比继承更灵活
- 可以动态添加/移除职责
缺点:
- 会产生许多小对象
- 过度使用会使系统复杂
- 调试困难(多层包装)
18.2 实战编码问题
Q3:实现一个带缓存的文件读取装饰器
A3:
java复制public class CachedFileReader extends Reader {
private final Reader delegate;
private final Map<Long, String> cache = new LRUMap<>(100);
public CachedFileReader(Reader delegate) {
this.delegate = delegate;
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
// 实现带缓存的读取逻辑
long position = ...; // 计算当前位置
if (cache.containsKey(position)) {
// 从缓存读取
} else {
// 委托给底层Reader
// 将结果存入缓存
}
}
}
Q4:如何处理装饰器中的异常?
A4:
- 装饰器应该透明传播异常,除非异常与其增强功能相关
- 可以包装原始异常,添加装饰器相关上下文
- 示例:
java复制try { return delegate.someMethod(); } catch (IOException e) { logger.error("Decorator failed", e); throw new EnhancedException("Decorator context", e); }
19. 个人实践心得
在多年的架构实践中,我总结了这些装饰器模式的使用心得:
-
命名体现装饰功能
- 好的装饰器名:
BufferedInputStream,SynchronizedList - 差的名字:
InputStreamWrapper,ListHelper
- 好的装饰器名:
-
文档明确装饰边界
java复制/** * 为InputStream添加行号计数功能 * 装饰后支持: * - getLineNumber() 获取当前行号 * 注意: * - 不改变原始读取语义 * - 线程不安全 */ public class LineNumberInputStream extends FilterInputStream { ... } -
控制装饰器可见性
- 内部装饰器使用包私有可见性
- 公共装饰器应设计为final类(除非明确需要继承)
-
性能敏感处慎用
- 在热点路径上,考虑手动内联装饰逻辑
- 或使用静态代理替代动态装饰
-
监控装饰器使用
java复制// 在装饰器中添加监控点 public class MonitoredDecorator implements Component { private final Counter counter; public void operation() { counter.increment(); long start = System.nanoTime(); try { delegate.operation(); } finally { metrics.recordTime(System.nanoTime() - start); } } }
20. 未来发展趋势
随着编程语言和范式的发展,装饰器模式也呈现出新的形态:
-
编译时装饰器(注解处理器)
- 通过编译时代码生成实现装饰
- 如Java注解处理器、Kotlin编译器插件
-
函数式装饰器
- 在函数式编程中,高阶函数天然支持装饰
- 如Kotlin扩展函数、Swift函数包装
-
响应式装饰器
- 在响应式流中装饰Publisher/Subscriber
- 如Project Reactor的transform操作符
-
云原生装饰器
- 在Service Mesh中,Sidecar作为网络装饰器
- 如Istio的Envoy过滤器链
-
AI生成的动态装饰
- 根据运行时分析自动生成和组合装饰器
- 如基于性能监控自动添加缓存装饰器
装饰器模式的核心思想——动态增强对象功能——将长期存在,但其实现形式会随着技术演进不断丰富。作为开发者,理解这个本质比记住具体实现更重要。
