文章目录
- 0.个人感悟
- 1.概念
- [2. 适配场景(什么场景下使用)](#2. 适配场景(什么场景下使用))
-
- [2.1 适合的场景](#2.1 适合的场景)
- [2.2 常见场景举例](#2.2 常见场景举例)
- [3. 实现方法](#3. 实现方法)
-
- [3.1 实现思路](#3.1 实现思路)
- [3.2 UML类图](#3.2 UML类图)
- [3.3 代码示例](#3.3 代码示例)
-
- [3.3.1 传统设计-继承](#3.3.1 传统设计-继承)
- [3.3.2 进阶模式-组合](#3.3.2 进阶模式-组合)
- [3.3.3 装饰者模式](#3.3.3 装饰者模式)
- [4. 优缺点](#4. 优缺点)
-
- [4.1 优点](#4.1 优点)
- [4.2 缺点](#4.2 缺点)
- [5. 源码分析:结合JDK的文件IO实现进行说明](#5. 源码分析:结合JDK的文件IO实现进行说明)
0.个人感悟
- 这个模式开始比较难懂。细究发现是想给主体添加额外的动作,一方面不想影响主体原功能,一方面可以动态扩展这些功能
- 其实纯用组合的方式来组合主体和辅助部分也可以解决问题,使用装饰者模式更贴合辅助是给主体打工的思想,添加辅助功能后它的身份还是主体。表现上,构造器代码可以嵌套,比如 new BufferedInputStream(new FileInputStream(new File("fileName")))
- 装饰者模式带来这种迭代效果的同时,也增加了系统复杂性,排查问题可能需要多层定位。实际工作中看情况使用
1.概念
英文定义 (《设计模式:可复用面向对象软件的基础》):
Attach additional responsibilities to an object dynamically. Decorators provide aflexibale alternative to subclassing for extending functionality.
中文翻译:
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰者模式比生成子类更为灵活。
理解:
- 核心思想 : 通过组合 而非继承的方式,在不改变原有对象结构的情况下,动态地扩展其功能
- 关键要点 :
- 透明包装: 装饰者和被装饰对象具有相同的接口,客户端无需知道是否被装饰
- 动态扩展: 可以在运行时根据需要动态地添加或移除装饰
- 多层嵌套: 可以多次装饰同一个对象,形成装饰链
- 开放封闭: 符合开闭原则,可以新增装饰者而不修改原有代码
2. 适配场景(什么场景下使用)
2.1 适合的场景
- 动态添加功能: 需要在不修改原有对象的情况下,动态地给对象添加额外的功能
- 替代多重继承: 当通过继承扩展功能会导致子类爆炸或类层次复杂时
- 撤销功能: 需要能够动态地添加或撤销功能
- 组合功能: 需要将多个功能以不同的组合方式添加到对象上
- 核心功能与辅助功能分离: 希望将核心功能与装饰性功能分离
2.2 常见场景举例
- Java I/O流 :
BufferedInputStream、DataInputStream等装饰InputStream - GUI组件: 为窗口添加滚动条、边框、标题栏等装饰
- Web服务器: 中间件装饰请求和响应对象,如添加日志、认证、压缩等功能
- 日志系统: 为日志记录器添加时间戳、线程信息、调用栈等装饰信息
- 权限控制: 为业务对象添加权限检查、审计等装饰功能
3. 实现方法
3.1 实现思路
实现装饰者模式通常遵循以下步骤:
- 定义组件接口: 创建一个接口或抽象类,定义被装饰对象和装饰者的共同行为
- 创建具体组件: 实现组件接口,这是被装饰的原始对象
- 创建装饰者抽象类 :
- 实现组件接口
- 持有一个组件接口的引用(用于包装其他组件)
- 将方法调用委托给持有的组件
- 创建具体装饰者: 继承装饰者抽象类,在调用被装饰对象的方法前后添加额外的行为
- 客户端使用: 客户端可以自由组合装饰者来装饰具体组件
3.2 UML类图

角色说明:
- Component (组件接口): 定义对象接口,可以动态地给这些对象添加职责
- ConcreteComponent (具体组件): 定义一个对象,可以给这个对象添加一些职责
- Decorator (装饰者抽象类): 维持一个指向Component对象的引用,并定义一个与Component接口一致的接口
- ConcreteDecorator (具体装饰者): 向组件添加具体的职责
3.3 代码示例
以星巴克饮料和调料为例:
星巴克有很多种饮料,比如咖啡、果汁等,点单的时候可以单点饮料,也可以加入调料,比如牛奶巧克力
3.3.1 传统设计-继承
传统设计,按照继承方式设计,定义父类drink,剩余所有单品都是一个类型
会出现类爆炸问题,而且不利于复用

3.3.2 进阶模式-组合
将调料作为饮品的属性进行组合
这样可以解决扩展问题,但是还是缺一些意思,没有很好地展现饮品和调料的主从关系,使用起来也不直观

3.3.3 装饰者模式
按照装饰者模式进行实现
组件及其具体实现:
java
// 饮料
public abstract class Drink {
protected String desc;
private double price;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public abstract double cost();
}
// 咖啡
public class Coffee extends Drink {
@Override
public double cost() {
return super.getPrice();
}
}
// 具体组件:意大利咖啡
public class Espresso extends Coffee {
public Espresso() {
setDesc(" 意大利咖啡 ");
setPrice(6.0d);
}
}
装饰器和具体实现
java
// 装饰器
public class Decorator extends Drink {
private Drink drink;
public Decorator(Drink drink) {
this.drink = drink;
}
@Override
public double cost() {
return super.getPrice() + drink.cost();
}
@Override
public String getDesc() {
return STR."\{desc} \{getPrice()} \{drink.getDesc()} ";
}
}
// 具体实现 牛奶
public class Milk extends Decorator {
public Milk(Drink drink) {
super(drink);
setDesc(" 牛奶 ");
setPrice(2.0d);
}
}
// 具体实现 巧克力
public class Chocolate extends Decorator {
public Chocolate(Drink drink) {
super(drink);
setDesc(" 巧克力 ");
setPrice(3.0f);
}
}
测试和输出
java
public class Client {
static void main() {
// 单点
Drink order = new Espresso();
System.out.println("===单点咖啡===");
printInfo(order);
// 加入牛奶
order = new Milk(order);
System.out.println("===加入牛奶===");
printInfo(order);
// 加入巧克力
order = new Chocolate(order);
System.out.println("===加入巧克力===");
printInfo(order);
// 再加入巧克力
order = new Chocolate(order);
System.out.println("===再加入巧克力===");
printInfo(order);
}
public static void printInfo(Drink drink) {
System.out.println(STR."描述 :\{drink.getDesc()}");
System.out.println(STR."价格 : \{drink.cost()}");
}
}
===单点咖啡===
描述 : 意大利咖啡
价格 : 6.0
===加入牛奶===
描述 : 牛奶 2.0 意大利咖啡
价格 : 8.0
===加入巧克力===
描述 : 巧克力 3.0 牛奶 2.0 意大利咖啡
价格 : 11.0
===再加入巧克力===
描述 : 巧克力 3.0 巧克力 3.0 牛奶 2.0 意大利咖啡
价格 : 14.0
4. 优缺点
4.1 优点
- 高内聚低耦合: 装饰者和被装饰者通过组合方式连接,耦合度低,符合合成复用原则
- 复用性: 装饰者类可以在不同的上下文中复用,用于装饰不同的组件对象
- 可读性: 虽然装饰者模式会增加类的数量,但每个类的职责单一,代码逻辑清晰
- 维护性: 符合开闭原则,可以新增装饰者而无需修改现有代码,易于维护和扩展
- 稳定性: 由于不修改原有对象,只是增加包装层,不会破坏原有系统的稳定性
- 单一职责: 可以将复杂的功能分解为多个简单的装饰者,每个装饰者只关注一个功能
- 动态组合: 可以在运行时动态地组合功能,提供了比继承更灵活的功能扩展方式
4.2 缺点
- 增加系统复杂性: 会增加许多小类,特别是装饰链较长时,系统会变得复杂
- 多层装饰调试困难: 由于对象被多层装饰,调试时可能需要逐层排查,增加了调试难度
- 装饰顺序影响结果: 装饰者的顺序可能会影响最终结果,需要特别注意装饰的顺序
- 初始化配置复杂: 客户端需要负责创建装饰者链,代码可能显得冗长
5. 源码分析:结合JDK的文件IO实现进行说明
跟踪代码,不难发现InputStream的类图

-
组件接口层次:
- 字节流:
InputStream(输入)、OutputStream(输出) - 字符流:
Reader(输入)、Writer(输出)
- 字节流:
-
具体组件:
FileInputStream、FileOutputStream、ByteArrayInputStream、ByteArrayOutputStreamFileReader、FileWriter、CharArrayReader、CharArrayWriter
-
装饰者抽象类:
FilterInputStream、FilterOutputStreamFilterReader、FilterWriter
-
具体装饰者:
- 缓冲功能:
BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter - 数据类型:
DataInputStream、DataOutputStream - 对象序列化:
ObjectInputStream、ObjectOutputStream - 回推功能:
PushbackInputStream、PushbackReader - 行号功能:
LineNumberReader - 打印功能:
PrintStream、PrintWriter
核心装饰者:
- 缓冲功能:
java
// 装饰者抽象类:FilterInputStream
public class FilterInputStream extends InputStream {
protected volatile InputStream in; // 持有一个InputStream的引用
protected FilterInputStream(InputStream in) {
this.in = in;
}
@Override
public int read() throws IOException {
return in.read(); // 委托给被装饰的InputStream
}
@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); // 委托给被装饰的InputStream
}
// 其他方法也类似地委托给in
}
参考: