- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
装饰器(Decorator)模式是 GoF 提出的经典结构性设计模式,其核心思想是动态地为对象附加额外职责,用对象组合替代子类继承来扩展功能。本文以 java-design-patterns 仓库中的decorator模块为骨架,通过完整的巨魔(Troll)实战示例、源码调用链与单元测试,讲解装饰器模式的定义、四类角色、实现要点、适用场景及 JDK 中的真实应用。读完本文,你将能够独立实现一个符合单一职责原则、支持运行时动态叠加职责的装饰器组件。
或称:包装器(Wrapper)
装饰器模式在业界常被称为包装器,因为它本质上就是用一个装饰类对象把目标对象"包装"起来:装饰类与目标实现同一接口,内部持有目标对象的引用,对外透明地转发调用,并在转发前后插入额外的行为。
目的
动态地为对象附加额外的职责。装饰器为子类提供了一种灵活的替代方案,以扩展功能。
- 动态:职责的叠加发生在运行时,而非编译期;
- 透明:对调用方来说,装饰后的对象与原始对象使用方式完全一致(同一接口);
- 替代继承:避免为了每种功能组合创建大量子类。
真实世界例子:愤怒的巨魔
附近的山丘上住着一个愤怒的巨魔。通常它是徒手的,但有时它有武器。为了武装巨魔不必创建新的巨魔,而是用合适的武器动态地装饰它。
通俗地说:装饰器模式让你可以在运行时通过把对象包装进一个装饰类对象中,来动态改变一个对象的行为。
维基百科给出的定义更加严谨:
在面向对象的编程中,装饰器模式是一种设计模式,它允许将行为静态或动态地添加到单个对象中,而不会影响同一类中其他对象的行为。装饰器模式通常对于遵守单一责任原则很有用,因为它允许将功能划分到具有唯一关注领域的类之间。
模式角色与类图
从 decorator/etc/decorator.urm.puml 中的 PlantUML 定义可以看出,本模块共有四个参与者,正好对应装饰器模式的四个标准角色:
| 角色 | 本模块实现类 | 职责 |
|---|---|---|
| 抽象构件(Component) | Troll接口 | 定义业务方法attack()、getAttackPower()、fleeBattle() |
| 具体构件(ConcreteComponent) | SimpleTroll | 实现接口的原始对象,被装饰的目标 |
| 装饰(Decorator) | ClubbedTroll | 持有Troll decorated引用,实现同一接口,转发调用并叠加行为 |
| 客户端(Client) | App | 运行时组装具体构件与装饰器 |
装饰器模式类图:Troll 接口由 SimpleTroll 与 ClubbedTroll 实现,ClubbedTroll 通过组合持有被装饰的 Troll 引用
类图中的关键关系:
SimpleTroll ..|> Troll:具体构件实现接口;ClubbedTroll ..|> Troll:装饰器也实现同一接口,保证类型透明;ClubbedTroll --> "-decorated" Troll:装饰器组合(而非继承)目标对象,这是整个模式能够动态叠加职责的根基。
程序示例:从徒手到持棒
下面以巨魔为例,从源码层面完整走一遍装饰器模式的实现。
第一步:抽象构件——巨魔接口
首先定义一个简单的巨魔接口,它声明了巨魔的全部行为。对应源码 decorator/src/main/java/com/iluwatar/decorator/Troll.java:
public interface Troll { void attack(); int getAttackPower(); void fleeBattle(); }第二步:具体构件——徒手巨魔
然后创建一个简单的巨魔,直接实现Troll接口,初始攻击力为 10。对应源码 decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java:
@Slf4j public class SimpleTroll implements Troll { @Override public void attack() { LOGGER.info("The troll tries to grab you!"); } @Override public int getAttackPower() { return 10; } @Override public void fleeBattle() { LOGGER.info("The troll shrieks in horror and runs away!"); } }注意这里使用了 Lombok 的@Slf4j注解生成日志对象,日志框架为 SLF4J + Logback,依赖声明可见 decorator/pom.xml。
第三步:装饰器——持棒巨魔
接下来我们想为巨魔添加一根球棒。按照装饰器模式,不修改SimpleTroll,也不为它创建子类,而是新建一个ClubbedTroll装饰器:它实现Troll接口,通过构造器注入被装饰的巨魔,并在转发调用时叠加行为。对应源码 decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java:
@Slf4j @RequiredArgsConstructor public class ClubbedTroll implements Troll { private final Troll decorated; @Override public void attack() { decorated.attack(); LOGGER.info("The troll swings at you with a club!"); } @Override public int getAttackPower() { return decorated.getAttackPower() + 10; } @Override public void fleeBattle() { decorated.fleeBattle(); } }这里@RequiredArgsConstructor为final字段decorated自动生成构造器。逐方法分析装饰器的行为:
attack():先调用decorated.attack()转发原始攻击,再追加"挥棒"日志——这是典型的前置/后置增强;getAttackPower():返回decorated.getAttackPower() + 10——把攻击力从 10 增强到 20;fleeBattle():原样转发,不做任何增强——装饰器可以选择性增强部分方法。
第四步:客户端组装——实战演示
程序入口 decorator/src/main/java/com/iluwatar/decorator/App.java 演示了"先徒手作战、再动态装备球棒"的完整过程:
// simple troll var troll = new SimpleTroll(); troll.attack(); // The troll tries to grab you! troll.fleeBattle(); // The troll shrieks in horror and runs away! LOGGER.info("Simple troll power: {}.\n", troll.getAttackPower()); // 10 // change the behavior of the simple troll by adding a decorator var clubbedTroll = new ClubbedTroll(troll); clubbedTroll.attack(); // The troll tries to grab you! The troll swings at you with a club! clubbedTroll.fleeBattle(); // The troll shrieks in horror and runs away! LOGGER.info("Clubbed troll power: {}.\n", clubbedTroll.getAttackPower()); // 20两个关键观察点:
- 运行时动态装饰:
troll对象本身从未被修改,ClubbedTroll只是把它"包"了一层,包装前后troll的行为完全不变,而clubbedTroll的行为被增强了; - 原始对象可复用:同一个
SimpleTroll实例既能独自作战,也能被任意多个装饰器反复包装,不影响同一类中其他对象的行为。
运行与测试验证
编译运行
本模块是一个独立的 Maven 工程,可通过 Maven 直接运行主类查看日志输出(主类com.iluwatar.decorator.App已在 decorator/pom.xml 的maven-assembly-plugin中配置):
mvn -pl decorator compile exec:java -Dexec.mainClass=com.iluwatar.decorator.App或在模块目录内直接执行:
mvn package java -cp target/decorator-*.jar com.iluwatar.decorator.App单元测试验证
仓库为装饰器模块提供了三个测试,从不同角度验证了模式行为:
SimpleTrollTest.java:验证具体构件的行为。断言getAttackPower()返回 10,attack()与fleeBattle()分别输出对应日志(通过自定义InMemoryAppender捕获 Logback 日志),并断言共产生 2 条日志。
ClubbedTrollTest.java:这是最有说服力的测试,使用 Mockito 的spy验证委托关系与增强逻辑:
final var simpleTroll = spy(new SimpleTroll()); final var clubbed = new ClubbedTroll(simpleTroll); assertEquals(20, clubbed.getAttackPower()); // 增强:10 + 10 verify(simpleTroll, times(1)).getAttackPower(); // 委托:增强基于原对象 clubbed.attack(); // 转发原始攻击 verify(simpleTroll, times(1)).attack(); clubbed.fleeBattle(); // 原样转发逃跑 verify(simpleTroll, times(1)).fleeBattle(); verifyNoMoreInteractions(simpleTroll);测试明确印证了装饰器的两个核心语义:增强逻辑确实发生在原对象结果之上(20 = 10 + 10),且所有调用都被委托给被装饰对象,装饰器自身没有引入额外状态。
AppTest.java:验证主方法App.main可无异常执行,保证示例可运行。
适用性:什么时候使用装饰器
根据原文档并结合源码结构,在以下场景中应当考虑使用装饰器模式:
- 动态透明地向单个对象添加职责,即不影响其他对象——如上例,给一个巨魔加球棒不影响其他徒手巨魔;
- 对于可以撤销的职责——装饰可以随时解除(去掉包装即恢复原对象),职责的叠加顺序也可控;
- 当通过子类化进行扩展不切实际时——有时可能存在大量独立的扩展,若为每种组合都建立子类,会产生组合爆炸。例如功能 A、B、C 的任意组合需要 2³ 种子类;而用装饰器,只需要 3 个装饰类即可在运行时任意叠加。此外,当类定义被隐藏或无法用于子类化时,装饰器是唯一可行的扩展手段。
装饰器在 Java 世界中的真实案例
原文档列出了装饰器模式在标准 JDK 中的经典应用,这些都是可以随时打开源码验证的实例:
java.io体系:java.io.InputStream、java.io.OutputStream、java.io.Reader、java.io.Writer 及其子类体系——例如BufferedInputStream、DataInputStream都是装饰器,层层包装同一抽象输入流,逐层叠加缓冲、类型化读取等能力;java.util.Collections的视图方法:Collections#synchronizedXXX()——为任意集合动态附加线程安全能力;Collections#unmodifiableXXX()——为任意集合动态附加只读保护;Collections#checkedXXX()——为任意集合动态附加类型检查。
这三个系列的共同特征是:传入一个普通集合,返回一个"行为被改变但接口相同"的包装集合,且原集合本身不受影响——与巨魔示例的机制完全一致。
实现要点与设计权衡
结合源码 ClubbedTroll.java 可以总结出四条可复用的实现准则:
- 装饰器与构件必须实现同一接口(
Troll),这是"透明"的前提,客户端无需感知对象是否被装饰; - 用组合而非继承:装饰器持有
final Troll decorated引用,在构造器中注入,保证职责可在运行时动态组合; - 转发与增强分离:需要增强的方法先
decorated.xxx()再追加逻辑(如attack()),需要原样传递的方法直接转发(如fleeBattle()),增强逻辑保持单一职责、互不干扰; - 注意顺序敏感性与"洋葱"结构:多个装饰器叠加时呈层层包裹的洋葱结构,调用顺序即包装顺序,叠加职责过多时调试成本会上升,因此装饰器应保持小而专一。
总结
装饰器模式用一个"包装器"类解决了子类组合爆炸与运行时扩展两大痛点:SimpleTroll负责核心行为,ClubbedTroll只负责"加球棒"这一单一职责,两者通过Troll接口松耦合,客户端在运行时自由组装。从 java-design-patterns 的 decorator 模块源码 及其 测试用例 可以确认:增强基于委托结果、调用透明转发、原对象零侵入——这正是装饰器模式在 JDK IO 流与 Collections 工具类中被广泛采用的底层原因。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
java-design-patterns 装饰器模式(Decorator Pattern)深度解析:用动态包装替代继承实现对象扩展
java design patterns 装饰器模式(Decorator Pattern)深度解析:用动态包装替代继承实现对象扩展 本文基于 java desi
示例工程教程java-design-patterns 中的 Decorator(装饰器)模式:在 Java 中动态扩展对象职责的完整实战指南
java design patterns 中的 Decorator(装饰器)模式:在 Java 中动态扩展对象职责的完整实战指南 本指南以 java desig
示例工程教程java-design-patterns 项目中的装饰器模式(Decorator):在 Java 中动态扩展对象职责的实战指南
java design patterns 项目中的装饰器模式(Decorator):在 Java 中动态扩展对象职责的实战指南 装饰器模式(Decorator)
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考