装饰模式(Decorator Pattern)
装饰模式是结构型设计模式的核心成员,其核心目标是动态地给一个对象添加一些额外的职责,且无需修改原对象的结构。它如同日常生活中 “给手机贴保护膜、装手机壳”—— 手机(原对象)的核心功能(通话、上网)不变,但通过保护膜(装饰)增加 “防刮” 功能,通过手机壳(装饰)增加 “防摔” 功能,且装饰可灵活组合(贴了膜也能装壳)。
一、装饰模式核心概念(通用)
1. 定义
动态地给一个对象添加一些额外的职责。就扩展功能而言,装饰模式比继承更灵活 —— 它通过 “组合” 而非 “继承” 实现功能扩展,避免了继承导致的类爆炸问题。
2. 意图
- 动态扩展功能:无需修改原对象代码,即可为对象新增功能(符合 “开闭原则”)。
- 避免多重继承:若用继承扩展多个功能,会产生 “功能组合爆炸”(如 “咖啡 + 牛奶 + 糖” 需创建
MilkSugarCoffee类),装饰模式通过组合实现灵活扩展。 - 功能可组合 / 撤销:多个装饰器可嵌套使用(如 “牛奶装饰 + 糖装饰”),若设计支持,还可撤销装饰(如移除糖的功能)。
3. 通用核心组件
装饰模式的核心是通过 “抽象装饰器” 持有 “抽象构件” 引用,实现功能的嵌套扩展,包含 4 个核心角色:
| 角色名称 | 职责描述 |
|---|---|
| Component(抽象构件) | 定义被装饰对象和装饰器的共同接口(或抽象类),声明核心业务方法(如咖啡的 “计算价格”“获取描述”)。 |
| ConcreteComponent(具体构件) | 实现抽象构件,是 “被装饰的原始对象”(如基础咖啡Espresso),提供核心业务逻辑的默认实现。 |
| Decorator(抽象装饰器) | 抽象类,实现抽象构件接口,持有一个 Component 类型的引用(指向被装饰的对象)。它的核心作用是 “传递业务方法调用”—— 在调用被装饰对象的方法前后,可添加额外逻辑(装饰功能)。 |
| ConcreteDecorator(具体装饰器) | 继承抽象装饰器,实现具体的装饰功能(如 “加牛奶”MilkDecorator、“加糖”SugarDecorator)。在重写的业务方法中,先调用被装饰对象的方法,再添加自身的装饰逻辑(如增加价格、补充描述)。 |
二、装饰模式详细解析(以 “咖啡加调料” 为例)
以 “咖啡订单系统” 为场景:基础咖啡(如Espresso、Latte)可添加多种调料(牛奶、糖、巧克力),每种调料需额外收费且需在订单描述中体现。若用继承实现,“Espresso + 牛奶 + 糖” 需创建MilkSugarEspresso类,功能组合越多,类数量爆炸;装饰模式通过 “基础咖啡 + 调料装饰器” 的组合,灵活实现任意调料搭配。
1. 结构
- Component(抽象构件):
Coffee,定义cost()(计算价格)和getDescription()(获取订单描述)方法。 - ConcreteComponent(具体构件):
Espresso(浓缩咖啡),实现cost()(基础价 20 元)和getDescription()(“浓缩咖啡”)。 - Decorator(抽象装饰器):
CoffeeDecorator,继承Coffee,持有Coffee引用,重写cost()和getDescription(),默认调用被装饰对象的方法(传递调用)。 - ConcreteDecorator(具体装饰器):
MilkDecorator(加牛奶,+5 元)、SugarDecorator(加糖,+3 元),重写方法时添加自身的价格和描述。
2. 类图(Mermaid)

classDiagram
%% 抽象构件:咖啡接口
class Coffee {
<<Abstract>>
+cost(): double // 计算价格
+getDescription(): String // 获取订单描述
}
%% 具体构件:浓缩咖啡(被装饰的原始对象)
class Espresso {
+Espresso()
+cost(): double // 基础价20元
+getDescription(): String // 描述:浓缩咖啡
}
%% 抽象装饰器:咖啡装饰器(持有Coffee引用)
class CoffeeDecorator {
<<Abstract>>
-coffee: Coffee // 持有被装饰的Coffee对象
+CoffeeDecorator(coffee: Coffee)
+cost(): double // 传递调用,可扩展
+getDescription(): String // 传递调用,可扩展
}
%% 具体装饰器1:加牛奶(+5元)
class MilkDecorator {
+MilkDecorator(coffee: Coffee)
+cost(): double // 被装饰对象价格 +5
+getDescription(): String // 被装饰对象描述 + ",加牛奶"
}
%% 具体装饰器2:加糖(+3元)
class SugarDecorator {
+SugarDecorator(coffee: Coffee)
+cost(): double // 被装饰对象价格 +3
+getDescription(): String // 被装饰对象描述 + ",加糖"
}
%% 关系梳理
%% 具体构件实现抽象构件
Coffee <|-- Espresso
%% 抽象装饰器实现抽象构件(保证接口一致)
Coffee <|-- CoffeeDecorator
%% 具体装饰器继承抽象装饰器
CoffeeDecorator <|-- MilkDecorator
%% 具体装饰器继承抽象装饰器
CoffeeDecorator <|-- SugarDecorator
CoffeeDecorator o-- Coffee : 持有(核心:装饰器关联被装饰对象)
3. 时序图(Mermaid)
以 “客户端点一杯‘浓缩咖啡 + 牛奶 + 糖’” 为例,展示装饰模式的调用流程(嵌套装饰的方法调用链):
sequenceDiagram
participant Client(客户端)
participant Espresso(具体构件:浓缩咖啡)
participant MilkDecorator(具体装饰器:加牛奶)
participant SugarDecorator(具体装饰器:加糖)
%% 1. 客户端创建基础咖啡(被装饰对象)
Client->>Espresso: new Espresso()
Espresso-->>Client: 返回Espresso实例
%% 2. 用牛奶装饰器包装基础咖啡
Client->>MilkDecorator: new MilkDecorator(Espresso)
MilkDecorator-->>Client: 返回包装后的MilkDecorator实例
%% 3. 用糖装饰器包装“牛奶咖啡”(嵌套装饰)
Client->>SugarDecorator: new SugarDecorator(MilkDecorator)
SugarDecorator-->>Client: 返回包装后的SugarDecorator实例
%% 4. 客户端调用最终装饰对象的cost()(计算总价格)
Client->>SugarDecorator: cost()
%% 4.1 糖装饰器调用牛奶装饰器的cost()
SugarDecorator->>MilkDecorator: cost()
%% 4.2 牛奶装饰器调用基础咖啡的cost()
MilkDecorator->>Espresso: cost()
%% 4.3 基础咖啡返回基础价格
Espresso-->>MilkDecorator: 20.0(基础价)
%% 4.4 牛奶装饰器加5元,返回25元
MilkDecorator-->>SugarDecorator: 25.0(20+5)
%% 4.5 糖装饰器加3元,返回28元
SugarDecorator-->>Client: 28.0(25+3)
%% 5. 客户端调用getDescription()(获取总描述)
Client->>SugarDecorator: getDescription()
SugarDecorator->>MilkDecorator: getDescription()
MilkDecorator->>Espresso: getDescription()
Espresso-->>MilkDecorator: "浓缩咖啡"
MilkDecorator-->>SugarDecorator: "浓缩咖啡,加牛奶"
SugarDecorator-->>Client: "浓缩咖啡,加牛奶,加糖"
4. 优点
- 动态灵活扩展:无需修改原对象代码,可在运行时为对象添加 / 移除功能(如给咖啡加 / 减牛奶),符合 “开闭原则”。
- 避免类爆炸:若用继承实现 “咖啡 + N 种调料”,需
2^N个类;装饰模式只需 “基础咖啡类 + N 个调料装饰器类”,类数量线性增长。 - 功能可组合:多个装饰器可嵌套使用(如 “牛奶 + 糖 + 巧克力”),实现任意功能组合。
- 符合合成复用原则:通过 “组合” 而非 “继承” 扩展功能,降低类间耦合(装饰器与被装饰对象通过接口关联)。
5. 缺点
- 增加系统复杂度:每新增一个功能需创建一个装饰器类,可能导致类数量增多(但比继承少);嵌套装饰会增加调试难度(需跟踪多层调用链)。
- 客户端需了解装饰器逻辑:客户端需知道如何组合装饰器(如 “先加牛奶再加糖”),若装饰器顺序影响结果(如 “先加冰再加糖” 和 “先加糖再加冰”),需客户端控制顺序。
- 不能替代继承的全部场景:若功能需修改原对象的核心逻辑(而非新增),装饰模式不适用(需用其他模式如策略模式)。
三、Java 代码实现(咖啡加调料示例)
1. 抽象构件(Coffee)
定义被装饰对象和装饰器的共同接口:
// 抽象构件:咖啡接口
public abstract class Coffee {
// 计算价格
public abstract double cost();
// 获取订单描述
public abstract String getDescription();
}
2. 具体构件(Espresso)
被装饰的原始对象,提供基础功能实现:
// 具体构件:浓缩咖啡(基础咖啡)
public class Espresso extends Coffee {
// 基础价格:20元
@Override
public double cost() {
return 20.0;
}
// 基础描述
@Override
public String getDescription() {
return "浓缩咖啡";
}
}
// (可选)其他具体构件:拿铁咖啡(基础价25元)
class Latte extends Coffee {
@Override
public double cost() {
return 25.0;
}
@Override
public String getDescription() {
return "拿铁咖啡";
}
}
3. 抽象装饰器(CoffeeDecorator)
持有 Coffee 引用,传递方法调用,为具体装饰器提供基础:
// 抽象装饰器:咖啡装饰器
public abstract class CoffeeDecorator extends Coffee {
// 持有被装饰的Coffee对象(核心关联)
protected Coffee coffee;
// 构造器注入被装饰对象
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
// 默认传递调用(具体装饰器可重写扩展)
@Override
public double cost() {
return coffee.cost();
}
@Override
public String getDescription() {
return coffee.getDescription();
}
}
4. 具体装饰器(MilkDecorator、SugarDecorator)
实现具体装饰功能,扩展原对象的方法:
// 具体装饰器1:加牛奶(+5元)
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
// 重写cost():基础价格 + 牛奶价格
@Override
public double cost() {
return coffee.cost() + 5.0;
}
// 重写getDescription():基础描述 + 牛奶描述
@Override
public String getDescription() {
return coffee.getDescription() + ",加牛奶";
}
}
// 具体装饰器2:加糖(+3元)
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return coffee.cost() + 3.0;
}
@Override
public String getDescription() {
return coffee.getDescription() + ",加糖";
}
}
// (可选)具体装饰器3:加巧克力(+8元)
class ChocolateDecorator extends CoffeeDecorator {
public ChocolateDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return coffee.cost() + 8.0;
}
@Override
public String getDescription() {
return coffee.getDescription() + ",加巧克力";
}
}
5. 客户端(Client)
通过组合装饰器,动态为基础咖啡添加功能:
// 客户端:点咖啡(组合装饰器)
public class Client {
public static void main(String[] args) {
// 场景1:基础浓缩咖啡(无装饰)
Coffee espresso = new Espresso();
System.out.println("订单1:" + espresso.getDescription() + ",总价:" + espresso.cost() + "元");
// 场景2:浓缩咖啡 + 牛奶
Coffee milkEspresso = new MilkDecorator(espresso);
System.out.println("订单2:" + milkEspresso.getDescription() + ",总价:" + milkEspresso.cost() + "元");
// 场景3:浓缩咖啡 + 牛奶 + 糖(嵌套装饰)
Coffee milkSugarEspresso = new SugarDecorator(milkEspresso);
System.out.println("订单3:" + milkSugarEspresso.getDescription() + ",总价:" + milkSugarEspresso.cost() + "元");
// 场景4:拿铁咖啡 + 巧克力 + 糖(换基础咖啡,组合其他装饰器)
Coffee latte = new Latte();
Coffee chocolateSugarLatte = new SugarDecorator(new ChocolateDecorator(latte));
System.out.println("订单4:" + chocolateSugarLatte.getDescription() + ",总价:" + chocolateSugarLatte.cost() + "元");
}
}
6. 输出结果
订单1:浓缩咖啡,总价:20.0元
订单2:浓缩咖啡,加牛奶,总价:25.0元
订单3:浓缩咖啡,加牛奶,加糖,总价:28.0元
订单4:拿铁咖啡,加巧克力,加糖,总价:36.0元
四、适用环境
装饰模式适用于以下场景,核心判断标准是 “需动态扩展功能,且避免用继承”:
- 动态扩展单个对象的功能:需为对象新增功能,且不影响其他同类对象(如给某杯咖啡加牛奶,不影响其他咖啡)。
- 避免多重继承导致的类爆炸:若用继承实现多个功能组合(如 “咖啡 + 牛奶 + 糖”),会产生大量组合类,装饰模式通过组合解决。
- 功能可组合 / 撤销:需灵活组合多个功能(如 “牛奶 + 糖 + 巧克力”),或需在运行时撤销功能(如给咖啡去掉糖)。
- 不能修改原对象代码:原对象属于第三方库或已稳定运行,不允许修改其源代码,需通过外部装饰扩展功能。
五、模式分析
1. 核心本质
装饰模式的本质是 “接口一致 + 组合嵌套 + 动态扩展”:
- 接口一致:装饰器与被装饰对象实现相同接口(
Coffee),确保客户端可透明使用(无需区分 “基础咖啡” 和 “装饰后的咖啡”)。 - 组合嵌套:装饰器通过持有
Coffee引用,实现 “装饰器包装装饰器” 的嵌套结构(如SugarDecorator(MilkDecorator(Espresso))),方法调用链自动传递。 - 动态扩展:通过新增装饰器类(如
ChocolateDecorator)扩展功能,无需修改原代码,符合 “开闭原则”。
2. 与其他模式的区别
| 模式 | 核心差异 | 典型场景 |
|---|---|---|
| 装饰模式 | 不改变接口,动态新增功能,组合嵌套 | 咖啡加调料、IO 流缓冲 |
| 适配器模式 | 改变接口(适配不兼容接口),不新增功能 | 旧系统接口适配新系统 |
| 桥接模式 | 分离抽象与实现,解决多维度变化,不新增功能 | 形状 + 颜色、遥控器 + 电视 |
| 继承 | 静态扩展功能,子类与父类强耦合 | 功能固定,无需动态扩展 |
3. 关键设计原则
- 接口一致性:装饰器必须实现与被装饰对象相同的接口(
Coffee),确保客户端可透明使用(“里氏替换原则” 的体现)。 - 装饰器轻量:具体装饰器应只负责单一功能(“单一职责原则”),如
MilkDecorator只处理 “加牛奶”,避免一个装饰器实现多个功能。 - 传递调用不可断:抽象装饰器的
cost()和getDescription()必须调用被装饰对象的对应方法(coffee.cost()),否则会中断调用链,导致基础功能丢失。
六、模式扩展
装饰模式可根据场景需求扩展出以下变体:
1. 透明装饰模式(Transparent Decorator)
- 特点:客户端完全不知道装饰器的存在,只通过抽象构件(
Coffee)接口操作对象(如客户端只调用Coffee的cost(),不关心是Espresso还是MilkDecorator)。 - 优点:客户端代码简洁,符合 “依赖倒置原则”(依赖抽象而非具体)。
- 缺点:若装饰器需新增额外方法(如
MilkDecorator的getMilkAmount()),客户端无法调用(需强制转型,破坏透明性)。
2. 半透明装饰模式(Semi-Transparent Decorator)
- 特点:客户端知道具体装饰器的存在,可调用装饰器的额外方法(如
MilkDecorator的setMilkAmount(100))。 - 实现:具体装饰器新增额外方法,客户端需将对象转型为具体装饰器类型才能调用。
- 优点:支持装饰器的额外功能调用。
- 缺点:破坏了接口透明性,客户端需依赖具体装饰器类,耦合度提高。
// 半透明装饰模式示例:MilkDecorator新增额外方法
class MilkDecorator extends CoffeeDecorator {
private int milkAmount; // 牛奶量(ml)
public MilkDecorator(Coffee coffee) {
super(coffee);
this.milkAmount = 50; // 默认50ml
}
// 额外方法:设置牛奶量
public void setMilkAmount(int amount) {
this.milkAmount = amount;
}
// 重写cost():牛奶量越多,价格越高
@Override
public double cost() {
return coffee.cost() + (milkAmount / 10.0); // 每10ml加1元
}
@Override
public String getDescription() {
return coffee.getDescription() + ",加" + milkAmount + "ml牛奶";
}
}
// 客户端调用额外方法(需转型)
Coffee milkEspresso = new MilkDecorator(new Espresso());
((MilkDecorator) milkEspresso).setMilkAmount(100); // 强制转型
System.out.println(milkEspresso.getDescription()); // 输出:浓缩咖啡,加100ml牛奶
System.out.println(milkEspresso.cost()); // 输出:20 + 10 = 30.0元
3. 可撤销装饰模式(Undoable Decorator)
- 特点:装饰器记录被装饰对象的原始状态,支持撤销装饰(移除新增的功能)。
- 实现:抽象装饰器新增
undo()方法,具体装饰器重写undo(),恢复被装饰对象的原始状态。
// 可撤销装饰模式示例
abstract class UndoableCoffeeDecorator extends CoffeeDecorator {
protected double originalCost; // 记录原始价格
protected String originalDesc; // 记录原始描述
public UndoableCoffeeDecorator(Coffee coffee) {
super(coffee);
this.originalCost = coffee.cost(); // 保存原始价格
this.originalDesc = coffee.getDescription(); // 保存原始描述
}
// 抽象撤销方法
public abstract void undo();
}
// 可撤销的牛奶装饰器
class UndoableMilkDecorator extends UndoableCoffeeDecorator {
public UndoableMilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return originalCost + 5.0;
}
@Override
public String getDescription() {
return originalDesc + ",加牛奶";
}
// 撤销装饰:恢复原始价格和描述(需通过成员变量记录)
@Override
public void undo() {
// 注:实际需设计状态保存机制,此处简化为打印撤销信息
System.out.println("撤销加牛奶,恢复为:" + originalDesc + ",价格:" + originalCost + "元");
}
}
// 客户端调用撤销
UndoableCoffeeDecorator milkEspresso = new UndoableMilkDecorator(new Espresso());
System.out.println("装饰后:" + milkEspresso.getDescription() + ",价格:" + milkEspresso.cost());
milkEspresso.undo(); // 输出:撤销加牛奶,恢复为:浓缩咖啡,价格:20.0元
七、模式应用(通用 + Android)
1. 通用领域应用
Java IO 流:装饰模式的经典应用!
InputStream/OutputStream是抽象构件,FileInputStream/FileOutputStream是具体构件,BufferedInputStream/DataInputStream是具体装饰器(如BufferedInputStream为 FileInputStream添加 “缓冲” 功能)。// Java IO流的装饰模式示例 InputStream in = new FileInputStream("test.txt"); // 具体构件 InputStream bufferedIn = new BufferedInputStream(in); // 装饰:加缓冲 InputStream dataIn = new DataInputStream(bufferedIn); // 嵌套装饰:加数据读取功能GUI 组件框架:如 Swing 中,
JComponent是抽象构件,JButton是具体构件,BorderDecorator(加边框)、ColorDecorator(加颜色)是装饰器,动态为按钮添加外观功能。日志框架:如 Log4j 中,
Appender是抽象构件,ConsoleAppender是具体构件,RollingFileAppender(滚动日志)、AsyncAppender(异步日志)是装饰器,扩展日志输出功能。
2. Android 中的应用
Android 框架大量使用装饰模式实现 “动态扩展组件功能”,核心场景集中在Context包装、UI 装饰和资源处理:
(1)ContextWrapper 与 Context 的装饰(核心应用)
- 角色对应:
Context:抽象构件(定义 Android 应用的核心上下文接口,如startActivity()、getSharedPreferences());ContextImpl:具体构件(Context的实际实现类,封装底层系统调用);ContextWrapper:抽象装饰器(继承Context,持有Context引用,传递方法调用);Activity/Service/Application:具体装饰器(继承ContextWrapper,扩展Context功能,如Activity新增setContentView(),Service新增startForeground())。
- 核心逻辑:Android 中所有
Context实例都是 “ContextImpl被ContextWrapper装饰” 的结果。例如,Activity的Context本质是:Activity(装饰器)→ContextWrapper(抽象装饰器)→ContextImpl(具体构件),Activity通过装饰扩展了Context的 UI 相关功能。
// Android源码简化逻辑(ContextWrapper)
public class ContextWrapper extends Context {
Context mBase; // 持有被装饰的Context(实际是ContextImpl)
public ContextWrapper(Context base) {
mBase = base;
}
// 传递调用:调用mBase的方法
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
// 其他方法均为传递调用...
}
// Activity作为具体装饰器,扩展Context功能
public class Activity extends ContextWrapper {
// 新增UI相关功能(装饰逻辑)
public void setContentView(int layoutResID) {
// 扩展:加载布局,初始化UI
getWindow().setContentView(layoutResID);
}
// 重写startActivity,添加Activity栈管理逻辑(装饰扩展)
@Override
public void startActivity(Intent intent) {
super.startActivity(intent); // 调用被装饰对象的方法
// 新增:记录Activity启动栈
ActivityTaskManager.getInstance().addActivityToStack(this);
}
}
(2)RecyclerView.ItemDecoration(UI 装饰)
- 角色对应:
RecyclerView的ViewHolder:抽象构件(展示 Item 的核心组件);- 自定义
ViewHolder:具体构件(实现 Item 的基础 UI); ItemDecoration:抽象装饰器(定义 Item 装饰接口,如onDraw()绘制分割线、getItemOffsets()设置偏移);DividerItemDecoration(系统提供):具体装饰器(实现 “绘制 Item 分割线” 的装饰功能)。
- 核心逻辑:通过
recyclerView.addItemDecoration(decoration)为ViewHolder动态添加装饰(如分割线、Item 间距),无需修改ViewHolder的代码,符合 “开闭原则”。
// Android中使用ItemDecoration装饰RecyclerView Item
RecyclerView recyclerView = findViewById(R.id.recycler_view);
// 添加分割线装饰(具体装饰器)
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(adapter); // adapter提供具体构件(ViewHolder)
(3)Window.DecorView(窗口装饰)
- 角色对应:
View:抽象构件(UI 组件的基类);ContentView(用户布局):具体构件(用户通过setContentView()设置的布局);DecorView:具体装饰器(继承FrameLayout,持有ContentView,为其添加系统装饰,如标题栏、状态栏、导航栏)。
- 核心逻辑:Android 窗口的
DecorView是 “用户布局的装饰器”—— 用户布局(ContentView)是核心内容,DecorView为其包装系统级 UI(如标题栏),动态扩展窗口功能。
八、总结
装饰模式是 “动态扩展功能” 的核心解决方案,其核心价值在于 “灵活、无侵入、可组合”:
- 核心优势:无需修改原代码即可动态扩展功能,避免继承导致的类爆炸,支持功能的灵活组合与撤销,符合 “开闭原则” 和 “合成复用原则”。
- 适用场景:需动态扩展单个对象功能、避免多重继承、不能修改原对象代码的场景(如 Java IO 流、Android Context 包装)。
- 实践建议:
- 确保装饰器与被装饰对象实现相同接口(保证透明性);
- 具体装饰器遵循 “单一职责原则”,每个装饰器只实现一个功能;
- 避免过度嵌套装饰(如超过 3 层),否则会增加调试难度和性能开销;
- 若需调用装饰器的额外功能,可使用半透明装饰模式,但需平衡透明性与功能需求。
装饰模式不仅是一种设计技巧,更是一种 “无侵入扩展” 的思维 —— 在不破坏原有系统的前提下,通过外部组合为对象新增能力,是维护大型系统稳定性和灵活性的重要工具
JDK
- java.io.BufferedInputStream(InputStream)
- java.io.DataInputStream(InputStream)
- java.io.BufferedOutputStream(OutputStream)
- java.util.zip.ZipOutputStream(OutputStream)
- java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap