装饰器模式:动态扩展对象功能的优雅设计
今天我们来深入探讨装饰器模式(Decorator Pattern),一种结构型设计模式,用于在不修改原有类的情况下动态扩展对象功能。装饰器模式通过将对象包装在装饰器类中,灵活添加新行为,遵循开闭原则。本文将带你实现一个简单的装饰器模式示例,适合初学者快速上手,同时为有经验的开发者提供进阶建议和优化思路。
装饰器模式在现实生活中类似手机壳,为核心对象增加额外功能。本文使用 Java 语言,通过一个咖啡订购系统展示装饰器模式的实现。让我们开始吧!
前置准备
在开始之前,确保开发环境已就绪:
-
JDK:推荐 JDK 17(也可使用 JDK 8+)。
-
IDE:IntelliJ IDEA、Eclipse 或 VS Code,推荐支持 Java 的 IDE。
-
构建工具:Maven(可选,用于管理依赖)。
-
项目结构 :创建一个简单的 Java 项目,目录如下:
decorator-pattern-demo ├── src │ ├── main │ │ ├── java │ │ │ └── com.example.decorator │ │ │ ├── beverage │ │ │ ├── decorator │ │ │ └── Main.java │ └── test └── pom.xml
安装环境:
- 确保 JDK 已安装:
java -version
. - Maven(可选):
mvn -version
. - 无需额外依赖,本示例使用纯 Java。
步骤 1: 定义抽象组件
装饰器模式需要一个抽象组件,定义核心功能。在 com.example.decorator.beverage.Beverage
中:
java
package com.example.decorator.beverage;
public interface Beverage {
String getDescription();
double cost();
}
说明:
Beverage
是目标接口,定义咖啡的描述和价格方法。
步骤 2: 创建具体组件
实现具体的咖啡类。在 com.example.decorator.beverage.Espresso
中:
java
package com.example.decorator.beverage;
public class Espresso implements Beverage {
@Override
public String getDescription() {
return "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
说明:
Espresso
是具体组件,提供基础咖啡功能。
步骤 3: 创建抽象装饰器
定义抽象装饰器类,包装组件并实现相同接口。在 com.example.decorator.decorator.CondimentDecorator
中:
java
package com.example.decorator.decorator;
import com.example.decorator.beverage.Beverage;
public abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public abstract String getDescription();
}
说明:
CondimentDecorator
持有Beverage
引用,允许嵌套装饰。
步骤 4: 创建具体装饰器
实现具体的装饰器类(如加奶、加糖)。在 com.example.decorator.decorator.Milk
中:
java
package com.example.decorator.decorator;
import com.example.decorator.beverage.Beverage;
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() + 0.50;
}
}
在 com.example.decorator.decorator.Sugar
中:
java
package com.example.decorator.decorator;
import com.example.decorator.beverage.Beverage;
public class Sugar extends CondimentDecorator {
public Sugar(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Sugar";
}
@Override
public double cost() {
return beverage.cost() + 0.20;
}
}
说明:
- 每个装饰器扩展功能(如添加"奶"或"糖"),并调整描述和价格。
步骤 5: 客户端代码
在 com.example.decorator.Main
中测试装饰器模式:
java
package com.example.decorator;
import com.example.decorator.beverage.Beverage;
import com.example.decorator.beverage.Espresso;
import com.example.decorator.decorator.Milk;
import com.example.decorator.decorator.Sugar;
public class Main {
public static void main(String[] args) {
// 基础咖啡
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
// 加奶
beverage = new Milk(beverage);
System.out.println(beverage.getDescription() + " $" + beverage.cost());
// 再加糖
beverage = new Sugar(beverage);
System.out.println(beverage.getDescription() + " $" + beverage.cost());
}
}
运行输出:
Espresso $1.99
Espresso, Milk $2.49
Espresso, Milk, Sugar $2.69
步骤 6: 运行和测试
-
编译和运行:
-
在 IDE 中运行
Main
类。 -
或使用命令行:
bashjavac src/main/java/com/example/decorator/*.java src/main/java/com/example/decorator/*/*.java java com.example.decorator.Main
-
-
测试用例:
- 验证基础咖啡的描述和价格。
- 验证添加奶和糖后,描述和价格正确累加。
- 测试多层装饰(如加两次奶)。
-
调试技巧:
- 添加日志:使用
System.out
或 SLF4J 记录装饰过程。 - 检查对象嵌套:在调试器中查看
beverage
的引用链。 - 异常处理:确保装饰器不会接收 null 对象。
- 添加日志:使用
进阶与最佳实践
-
多层装饰:
-
支持动态添加多个装饰器:
javaBeverage beverage = new Sugar(new Milk(new Espresso()));
-
-
异常处理:
-
添加输入验证:
javapublic CondimentDecorator(Beverage beverage) { if (beverage == null) { throw new IllegalArgumentException("Beverage cannot be null"); } this.beverage = beverage; }
-
-
性能优化:
-
缓存描述字符串,避免重复计算:
javaprivate String cachedDescription; @Override public String getDescription() { if (cachedDescription == null) { cachedDescription = beverage.getDescription() + ", Milk"; } return cachedDescription; }
-
-
测试:
-
使用 JUnit 编写单元测试:
javaimport org.junit.Test; import static org.junit.Assert.*; public class DecoratorTest { @Test public void testEspressoWithMilkAndSugar() { Beverage beverage = new Sugar(new Milk(new Espresso())); assertEquals("Espresso, Milk, Sugar", beverage.getDescription()); assertEquals(2.69, beverage.cost(), 0.01); } }
-
-
其他应用场景:
- I/O 流(如 Java 的
InputStream
和BufferedInputStream
)。 - UI 组件装饰(如添加边框或滚动条)。
- I/O 流(如 Java 的
-
资源推荐:书籍《设计模式:可复用面向对象软件的基础》、Refactoring Guru 网站。多实践其他设计模式(如代理模式、工厂模式)。
总结
通过这个装饰器模式示例,你学会了如何动态扩展对象功能,实现了咖啡订购系统的灵活配置。装饰器模式在需要动态添加行为时非常实用,广泛应用于框架设计和功能扩展。