文章目录
- 一、概述
-
- [1.1 结构与角色](#1.1 结构与角色)
- [1.2 适用场景](#1.2 适用场景)
- 二、实现方式
-
- [2.1 基础实现------咖啡调味系统](#2.1 基础实现——咖啡调味系统)
- [2.2 继承 vs 装饰者对比](#2.2 继承 vs 装饰者对比)
- [2.3 实际应用示例------文本处理系统](#2.3 实际应用示例——文本处理系统)
- [三、JDK 源码中的装饰者](#三、JDK 源码中的装饰者)
-
- [3.1 Java I/O 体系](#3.1 Java I/O 体系)
- [3.2 Collections.unmodifiableList](#3.2 Collections.unmodifiableList)
- [3.3 Servlet API------HttpServletRequestWrapper](#3.3 Servlet API——HttpServletRequestWrapper)
- [四、装饰者 vs 代理模式](#四、装饰者 vs 代理模式)
-
- [4.1 意图对比](#4.1 意图对比)
- [4.2 结构对比](#4.2 结构对比)
- [4.3 代码对比](#4.3 代码对比)
- [4.4 选择指南](#4.4 选择指南)
- 五、总结
一、概述
在软件开发中,经常会遇到这样的场景:需要为一个对象动态地 添加新的功能,而不是通过继承来扩展。如果使用继承来实现,每增加一种功能组合就需要创建一个新的子类,导致类的数量呈指数级增长。例如,一杯咖啡可以加牛奶、加糖、加摩卡......如果用继承来表示每一种组合,类的数量将难以维护。
装饰者模式(Decorator Pattern)正是为了解决这个问题而诞生的------它动态地给对象增加新的功能,同时又不改变其结构。装饰者模式是替代继承的一种方案,它通过创建包装对象(即装饰者)来包裹真实对象,并在保持接口一致的前提下,增强或扩展对象的功能。
生活中的装饰者例子比比皆是:
- 咖啡调味:一杯黑咖啡(被装饰者)可以加牛奶(装饰者A)、加糖(装饰者B)、加摩卡(装饰者C),这些调料可以自由组合,层层包裹
- 穿衣搭配:一个人(被装饰者)可以穿T恤(装饰者A)、穿外套(装饰者B)、戴帽子(装饰者C),衣物可以逐层穿戴
- 礼品包装:一份礼物(被装饰者)可以包一层彩纸(装饰者A)、再系一条丝带(装饰者B)、再贴一朵花(装饰者C),层层装饰
核心:动态地给对象增加新的功能,同时不改变其结构,是替代继承的一种方案
1.1 结构与角色
装饰者模式包含以下角色:
实现
实现
持有
继承
继承
调用
Client 客户端
Component 抽象构件
ConcreteComponent 具体构件
Decorator 抽象装饰者
ConcreteDecoratorA 具体装饰者A
ConcreteDecoratorB 具体装饰者B
- Component(抽象构件):定义对象的接口,可以给这些对象动态添加功能
- ConcreteComponent(具体构件):定义一个具体的对象,也可以给这个对象添加一些功能
- Decorator(抽象装饰者):持有一个抽象构件的引用,并实现抽象构件的接口,通过子类扩展具体构件的功能
- ConcreteDecorator(具体装饰者):继承抽象装饰者,负责给构件添加新的功能
- Client(客户端):通过抽象构件接口与对象交互,无需关心对象是否被装饰
1.2 适用场景
- 需要动态地给对象添加功能,且这些功能可以动态撤销
- 需要组合多种功能,使用继承导致子类爆炸
- 不能采用继承的方式对系统进行扩展,或者继承不利于系统扩展和维护时
二、实现方式
装饰者模式的核心实现思路是:定义一个抽象装饰者类实现抽象构件接口,并持有抽象构件的引用,在具体装饰者中通过委托调用被装饰者的方法,并在其前后添加新的功能。
2.1 基础实现------咖啡调味系统
以咖啡店为例,一杯咖啡(被装饰者)可以加牛奶、加糖、加摩卡等调料(装饰者),调料可以自由组合:
实现
实现
持有
继承
继承
继承
客户端
Drink 抽象饮品
Coffee 咖啡
Decorator 调料装饰者
Milk 牛奶
Sugar 糖
Mocha 摩卡
(1)抽象构件------饮品
java
/**
* 抽象构件:饮品
*/
public abstract class Drink {
/**
* 描述
*/
protected String description = "未知饮品";
/**
* 获取描述
*
* @return 描述信息
*/
public String getDescription() {
return description;
}
/**
* 计算价格
*
* @return 价格
*/
public abstract double cost();
}
(2)具体构件------咖啡
java
/**
* 具体构件:咖啡
*/
public class Coffee extends Drink {
public Coffee() {
description = "咖啡";
}
@Override
public double cost() {
return 10.0;
}
}
(3)抽象装饰者------调料装饰者
java
/**
* 抽象装饰者:调料装饰者
* 继承 Drink 并持有 Drink 的引用
*/
public abstract class DrinkDecorator extends Drink {
/**
* 持有抽象构件的引用
*/
protected Drink drink;
public DrinkDecorator(Drink drink) {
this.drink = drink;
}
/**
* 抽象方法:获取描述,由具体装饰者实现
*/
@Override
public abstract String getDescription();
}
(4)具体装饰者------牛奶、糖、摩卡
java
/**
* 具体装饰者:牛奶
*/
public class Milk extends DrinkDecorator {
public Milk(Drink drink) {
super(drink);
}
@Override
public String getDescription() {
return drink.getDescription() + " + 牛奶";
}
@Override
public double cost() {
return drink.cost() + 3.0;
}
}
/**
* 具体装饰者:糖
*/
public class Sugar extends DrinkDecorator {
public Sugar(Drink drink) {
super(drink);
}
@Override
public String getDescription() {
return drink.getDescription() + " + 糖";
}
@Override
public double cost() {
return drink.cost() + 1.0;
}
}
/**
* 具体装饰者:摩卡
*/
public class Mocha extends DrinkDecorator {
public Mocha(Drink drink) {
super(drink);
}
@Override
public String getDescription() {
return drink.getDescription() + " + 摩卡";
}
@Override
public double cost() {
return drink.cost() + 5.0;
}
}
(5)客户端调用
java
public class CoffeeShopDemo {
public static void main(String[] args) {
// 1. 一杯普通咖啡
Drink coffee = new Coffee();
System.out.println(coffee.getDescription() + " ¥" + coffee.cost());
// 咖啡 ¥10.0
// 2. 咖啡 + 牛奶
Drink milkCoffee = new Milk(coffee);
System.out.println(milkCoffee.getDescription() + " ¥" + milkCoffee.cost());
// 咖啡 + 牛奶 ¥13.0
// 3. 咖啡 + 牛奶 + 糖
Drink milkSugarCoffee = new Sugar(milkCoffee);
System.out.println(milkSugarCoffee.getDescription() + " ¥" + milkSugarCoffee.cost());
// 咖啡 + 牛奶 + 糖 ¥14.0
// 4. 咖啡 + 摩卡 + 牛奶 + 糖
Drink fullCoffee = new Mocha(milkSugarCoffee);
System.out.println(fullCoffee.getDescription() + " ¥" + fullCoffee.cost());
// 咖啡 + 牛奶 + 糖 + 摩卡 ¥19.0
}
}
关键点 :每个具体装饰者都持有一个
Drink对象的引用,通过委托调用被装饰者的方法,并在其基础上添加自己的功能(修改描述和增加价格)。装饰者可以层层嵌套,每层添加一个新功能。
2.2 继承 vs 装饰者对比
如果用继承来实现咖啡调味系统,类的数量将急剧增长:
Coffee 咖啡
MilkCoffee 牛奶咖啡
SugarCoffee 糖咖啡
MochaCoffee 摩卡咖啡
MilkSugarCoffee 牛奶糖咖啡
MilkMochaCoffee 牛奶摩卡咖啡
SugarMochaCoffee 糖摩卡咖啡
MilkSugarMochaCoffee 牛奶糖摩卡咖啡
| 对比维度 | 继承方式 | 装饰者模式 |
|---|---|---|
| 类的数量 | (2^n - 1)(n 为调料种类) | 1 + n(1 个具体构件 + n 个具体装饰者) |
| 扩展新功能 | 需要创建大量子类 | 只需新增一个具体装饰者类 |
| 功能组合 | 编译时确定,静态组合 | 运行时动态组合 |
| 功能撤销 | 不支持 | 移除装饰者即可撤销 |
| 灵活性 | 低 | 高 |
对比:3 种调料用继承需要 7 个子类,而装饰者模式只需 3 个具体装饰者类 + 1 个具体构件类 = 4 个类。每新增一种调料,继承方式需要翻倍的子类,而装饰者模式只需 +1 个类。
2.3 实际应用示例------文本处理系统
以文本处理系统为例,文本可以经过多种处理器层层处理:加密、压缩、Base64 编码等,处理器可以自由组合:
实现
实现
持有
继承
继承
继承
客户端
TextProcessor 抽象文本处理器
PlainText 纯文本
ProcessorDecorator 处理器装饰者
EncryptionProcessor 加密处理器
CompressionProcessor 压缩处理器
Base64Processor Base64处理器
(1)抽象构件------文本处理器
java
/**
* 抽象构件:文本处理器
*/
public interface TextProcessor {
/**
* 处理文本
*
* @param text 原始文本
* @return 处理后的文本
*/
String process(String text);
}
(2)具体构件------纯文本
java
/**
* 具体构件:纯文本处理器
*/
public class PlainText implements TextProcessor {
@Override
public String process(String text) {
return text;
}
}
(3)抽象装饰者------处理器装饰者
java
/**
* 抽象装饰者:处理器装饰者
*/
public abstract class ProcessorDecorator implements TextProcessor {
/**
* 持有文本处理器的引用
*/
protected TextProcessor processor;
public ProcessorDecorator(TextProcessor processor) {
this.processor = processor;
}
@Override
public String process(String text) {
return processor.process(text);
}
}
(4)具体装饰者------加密、压缩、Base64
java
/**
* 具体装饰者:加密处理器
*/
public class EncryptionProcessor extends ProcessorDecorator {
private static final int SHIFT = 3;
public EncryptionProcessor(TextProcessor processor) {
super(processor);
}
@Override
public String process(String text) {
String processed = super.process(text);
return encrypt(processed);
}
/**
* 简单的凯撒加密
*/
private String encrypt(String text) {
StringBuilder result = new StringBuilder();
for (char c : text.toCharArray()) {
if (Character.isLetter(c)) {
char base = Character.isUpperCase(c) ? 'A' : 'a';
result.append((char) ((c - base + SHIFT) % 26 + base));
} else {
result.append(c);
}
}
return "[加密]" + result.toString();
}
}
/**
* 具体装饰者:压缩处理器
*/
public class CompressionProcessor extends ProcessorDecorator {
public CompressionProcessor(TextProcessor processor) {
super(processor);
}
@Override
public String process(String text) {
String processed = super.process(text);
return "[压缩]" + processed.replace(" ", "");
}
}
/**
* 具体装饰者:Base64 处理器
*/
public class Base64Processor extends ProcessorDecorator {
public Base64Processor(TextProcessor processor) {
super(processor);
}
@Override
public String process(String text) {
String processed = super.process(text);
return "[Base64]" + java.util.Base64.getEncoder().encodeToString(processed.getBytes());
}
}
(5)客户端调用
java
public class TextProcessingDemo {
public static void main(String[] args) {
String text = "Hello World";
// 1. 纯文本
TextProcessor plain = new PlainText();
System.out.println("纯文本:" + plain.process(text));
// 纯文本:Hello World
// 2. 先压缩,再加密
TextProcessor compressAndEncrypt = new EncryptionProcessor(new CompressionProcessor(plain));
System.out.println("压缩+加密:" + compressAndEncrypt.process(text));
// 压缩+加密:[加密][压缩]HelloWorld
// 3. 先加密,再 Base64
TextProcessor encryptAndBase64 = new Base64Processor(new EncryptionProcessor(plain));
System.out.println("加密+Base64:" + encryptAndBase64.process(text));
// 加密+Base64:[Base64]W2Tmt3Nd]Sbpplq...
// 4. 先压缩,再加密,最后 Base64
TextProcessor all = new Base64Processor(
new EncryptionProcessor(
new CompressionProcessor(plain)));
System.out.println("全部处理:" + all.process(text));
}
}
优势体现 :处理器的执行顺序可以自由组合,只需要改变装饰者的嵌套顺序即可。如果未来需要新增"签名处理器",只需创建一个
SignatureProcessor具体装饰者类,无需修改任何已有代码。
三、JDK 源码中的装饰者
装饰者模式在 JDK 中有着广泛的应用,最典型的就是 Java I/O 体系。
3.1 Java I/O 体系
Java 的 I/O 流是装饰者模式的经典应用。InputStream 是抽象构件,FileInputStream、ByteArrayInputStream 等是具体构件,FilterInputStream 是抽象装饰者,BufferedInputStream、DataInputStream 等是具体装饰者。
实现
继承
持有
继承
继承
InputStream 抽象构件
FileInputStream 具体构件
FilterInputStream 抽象装饰者
BufferedInputStream 具体装饰者
DataInputStream 具体装饰者
(1)抽象构件------InputStream
java
public abstract class InputStream implements Closeable {
/**
* 读取一个字节
*/
public abstract int read() throws IOException;
/**
* 读取多个字节到数组
*/
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* 读取多个字节到数组(指定偏移和长度)
*/
public int read(byte[] b, int off, int len) throws IOException {
// ... 默认实现
}
}
(2)抽象装饰者------FilterInputStream
java
public class FilterInputStream extends InputStream {
/**
* 持有抽象构件的引用
*/
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
@Override
public int read() throws IOException {
return in.read();
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return in.read(b, off, len);
}
@Override
public void close() throws IOException {
in.close();
}
}
(3)具体装饰者------BufferedInputStream
java
public class BufferedInputStream extends FilterInputStream {
/**
* 缓冲区大小
*/
private static final int DEFAULT_BUFFER_SIZE = 8192;
/**
* 内部缓冲区
*/
protected volatile byte[] buf;
public BufferedInputStream(InputStream in) {
super(in);
buf = new byte[DEFAULT_BUFFER_SIZE];
}
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
/**
* 读取一个字节(带缓冲)
* 在被装饰者基础上增加了缓冲功能
*/
@Override
public int read() throws IOException {
// 委托调用父类方法,并增加缓冲功能
// ... 缓冲逻辑
}
}
在这个例子中:
- Component(抽象构件) :
InputStream - ConcreteComponent(具体构件) :
FileInputStream、ByteArrayInputStream - Decorator(抽象装饰者) :
FilterInputStream - ConcreteDecorator(具体装饰者) :
BufferedInputStream、DataInputStream、PushbackInputStream
使用方式------装饰者层层嵌套:
java
// 基础构件:文件输入流
InputStream fileInputStream = new FileInputStream("data.txt");
// 加缓冲装饰
InputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
// 加数据读取装饰
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
// 读取数据
int value = dataInputStream.readInt();
说明 :
BufferedInputStream在FileInputStream的基础上增加了缓冲功能,DataInputStream在BufferedInputStream的基础上增加了读取基本数据类型的功能。三层装饰者层层包裹,每层各司其职。
3.2 Collections.unmodifiableList
java.util.Collections 中的不可变集合方法也是装饰者模式的应用。UnmodifiableList 包装一个 List,在保持 List 接口不变的前提下,将所有修改操作替换为抛出 UnsupportedOperationException。
java
public class Collections {
/**
* 返回不可修改的 List 视图
* 装饰者模式:包装原 List,禁止修改操作
*/
@SuppressWarnings("unchecked")
public static <T> List<T> unmodifiableList(List<? extends T> list) {
return (list instanceof RandomAccess)
? new UnmodifiableRandomAccessList<>(list)
: new UnmodifiableList<>(list);
}
}
/**
* 具体装饰者:不可修改的 List
*/
static class UnmodifiableList<E> extends UnmodifiableCollection<E>
implements List<E> {
final List<? extends E> list;
UnmodifiableList(List<? extends E> list) {
super(list);
this.list = list;
}
// 读取操作------委托给原 List
@Override
public E get(int index) {
return list.get(index);
}
// 修改操作------抛出异常
@Override
public E set(int index, E element) {
throw new UnsupportedOperationException();
}
@Override
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
@Override
public E remove(int index) {
throw new UnsupportedOperationException();
}
}
在这个例子中:
- Component(抽象构件) :
List<E> - ConcreteComponent(具体构件) :
ArrayList、LinkedList等具体的 List - Decorator(具体装饰者) :
UnmodifiableList(限制修改操作)
使用方式:
java
List<String> mutableList = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 装饰为不可修改的 List
List<String> unmodifiable = Collections.unmodifiableList(mutableList);
// 读取------正常工作
unmodifiable.get(0); // "A"
// 修改------抛出异常
unmodifiable.add("D"); // UnsupportedOperationException
3.3 Servlet API------HttpServletRequestWrapper
在 Servlet API 中,HttpServletRequestWrapper 是装饰者模式的典型应用。它实现了 HttpServletRequest 接口,并持有一个 HttpServletRequest 引用,所有方法默认委托给原对象。开发者可以继承 HttpServletRequestWrapper,只需重写需要修改的方法。
java
/**
* 抽象装饰者:HttpServletRequestWrapper
* 所有方法默认委托给原 HttpServletRequest
*/
public class HttpServletRequestWrapper implements HttpServletRequest {
private final HttpServletRequest request;
public HttpServletRequestWrapper(HttpServletRequest request) {
this.request = request;
}
@Override
public String getParameter(String name) {
return request.getParameter(name);
}
@Override
public String getHeader(String name) {
return request.getHeader(name);
}
// ... 其他方法均委托给 request
}
开发者可以继承 HttpServletRequestWrapper,重写特定方法来实现自定义逻辑:
java
/**
* 具体装饰者:XSS 防护请求包装器
* 重写 getParameter 方法,过滤危险字符
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value != null) {
// 过滤 XSS 危险字符
value = value.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
return value;
}
}
说明 :
HttpServletRequestWrapper让开发者只需关注需要修改的方法,而不需要实现HttpServletRequest的所有方法。这是装饰者模式简化扩展的典型体现。
四、装饰者 vs 代理模式
装饰者模式和代理模式都属于结构型模式,而且结构非常相似------都持有目标对象的引用,都实现了相同的接口,都通过委托来调用目标对象的方法。但它们的意图 和使用场景截然不同。
关于代理模式可参见博客:设计模式-代理模式
4.1 意图对比
| 对比维度 | 装饰者模式 | 代理模式 |
|---|---|---|
| 核心意图 | 动态地给对象增加功能 | 控制对对象的访问 |
| 解决的问题 | 功能组合导致的子类爆炸 | 访问控制、延迟加载、远程访问 |
| 对象关系 | 装饰者与被装饰者是平等的,可以层层嵌套 | 代理与被代理者是替代关系,客户端不直接访问被代理者 |
| 对象数量 | 可以有多个装饰者同时装饰一个对象 | 通常一个代理对应一个被代理者 |
| 对象创建 | 由客户端动态组合 | 代理对象可能自行创建被代理者 |
| 接口一致 | 两者都实现相同的接口 | 两者都实现相同的接口 |
4.2 结构对比
代理模式
持有
Subject
Proxy
RealSubject
装饰者模式
持有
Component
Decorator
ConcreteDecorator
装饰者模式:Decorator 持有 Component 的引用,与 Component 是平等的,客户端知道被装饰者是谁,装饰者由客户端动态组合。
代理模式:Proxy 持有 RealSubject 的引用,Proxy 是 RealSubject 的替代品,客户端通常不知道 RealSubject 的存在,Proxy 控制对 RealSubject 的访问。
4.3 代码对比
以"图片加载"为例,对比两种模式的应用方式:
装饰者模式的用法------给图片加载增加额外功能:
java
// 抽象构件
public interface Image {
void display();
}
// 具体构件
public class SimpleImage implements Image {
@Override
public void display() {
System.out.println("显示图片");
}
}
// 装饰者:添加边框功能
public class BorderDecorator implements Image {
private final Image image;
public BorderDecorator(Image image) {
this.image = image;
}
@Override
public void display() {
System.out.println("添加边框");
image.display();
}
}
// 装饰者:添加滤镜功能
public class FilterDecorator implements Image {
private final Image image;
public FilterDecorator(Image image) {
this.image = image;
}
@Override
public void display() {
System.out.println("添加滤镜");
image.display();
}
}
// 客户端:自由组合装饰者
Image image = new FilterDecorator(new BorderDecorator(new SimpleImage()));
image.display();
// 添加滤镜
// 添加边框
// 显示图片
代理模式的用法------控制图片的延迟加载:
java
// 抽象主题
public interface Image {
void display();
}
// 真实主题
public class HighResolutionImage implements Image {
private final String filename;
public HighResolutionImage(String filename) {
// 加载大文件------耗时操作
System.out.println("从磁盘加载高清图片:" + filename);
this.filename = filename;
}
@Override
public void display() {
System.out.println("显示高清图片:" + filename);
}
}
// 代理:延迟加载
public class ImageProxy implements Image {
private final String filename;
private HighResolutionImage realImage;
public ImageProxy(String filename) {
this.filename = filename;
}
@Override
public void display() {
// 延迟加载------只在真正需要时才创建真实对象
if (realImage == null) {
realImage = new HighResolutionImage(filename);
}
realImage.display();
}
}
// 客户端:通过代理访问
Image image = new ImageProxy("photo.jpg");
image.display(); // 第一次调用时才加载
关键区别 :装饰者模式关注的是功能增强 ,客户端主动组合装饰者来添加功能;代理模式关注的是访问控制,代理对象代替真实对象控制访问,客户端不需要知道真实对象的存在。
4.4 选择指南
| 场景 | 选择 |
|---|---|
| 需要动态组合多种功能 | 装饰者模式 |
| 需要在不修改已有代码的前提下扩展功能 | 装饰者模式 |
| 功能需要层层叠加,且可以自由组合 | 装饰者模式 |
| 需要控制对象的访问权限 | 代理模式 |
| 需要延迟加载(懒加载) | 代理模式 |
| 需要访问远程对象 | 代理模式 |
| 需要在不修改源码的前提下增加缓存功能 | 代理模式 |
五、总结
装饰者模式的核心思想是动态地给对象增加新的功能,通过创建包装对象来包裹真实对象,在不改变其结构的前提下扩展功能,是替代继承的一种方案。
优点:
- 动态扩展:可以在运行时动态地给对象添加功能,比继承更灵活
- 避免子类爆炸:用组合代替继承,功能组合不需要创建大量子类
- 符合开闭原则:新增功能只需新增具体装饰者类,无需修改已有代码
- 功能可撤销:移除装饰者即可撤销对应的功能
- 功能可组合:多个装饰者可以自由组合,顺序灵活
缺点:
- 增加系统复杂度:多了一层装饰者,增加了类的数量
- 调试困难:多层装饰者嵌套后,调试时不容易理清调用关系
- 装饰者顺序敏感:不同的装饰顺序可能产生不同的结果
- 特殊类型问题:装饰者包装后,无法通过
instanceof判断是否为特定具体构件
适用场景:
- 需要动态地给对象添加功能,且可以动态撤销
- 需要组合多种功能,使用继承导致子类爆炸
- 不能通过继承来扩展系统(如 final 类),或继承不利于系统维护
- 需要在不修改已有代码的前提下扩展对象的功能
装饰者 vs 代理 :装饰者模式的意图是
功能增强,客户端主动组合装饰者来添加功能;代理模式的意图是控制访问,代理对象代替真实对象控制访问,客户端通常不直接接触真实对象。两者结构相似,但意图截然不同。
参考博客:
装饰器模式 | 菜鸟教程:https://www.runoob.com/design-pattern/decorator-pattern.html