1 基本介绍
装饰者模式(Decorator Pattern)是一种 结构型 设计模式,它 允许向一个现有的对象动态地添加新的功能,同时不改变其结构。该模式通过创建一个 包装对象(即装饰者)来 包裹 真实对象,并在 保持类方法签名完整性 的前提下,给对象 添加一些新的职责。
2 案例
2.1 Noodles 抽象类
public abstract class Noodles { // 面食
public abstract String desc(); // 获取面的描述
}
2.2 ShavedNoodles 类
public class ShavedNoodles extends Noodles { // 刀削面
@Override
public String desc() {
return "刀削面";
}
}
2.3 HandPulledNoodles 类
public class HandPulledNoodles extends Noodles { // 拉面
@Override
public String desc() {
return "拉面";
}
}
2.4 Flavoring 抽象类
public abstract class Flavoring extends Noodles { // 调味料
protected Noodles noodles; // 面食
public Flavoring(Noodles noodles) {
this.noodles = noodles;
}
}
2.5 Pepper 类
public class Pepper extends Flavoring { // 辣椒
public Pepper(Noodles noodles) {
super(noodles);
}
@Override
public String desc() {
return "多加一份[辣椒]的 " + noodles.desc();
}
}
2.6 Salt 类
public class Salt extends Flavoring { // 食盐
public Salt(Noodles noodles) {
super(noodles);
}
@Override
public String desc() {
return "多加一份[食盐]的 " + noodles.desc();
}
}
2.7 Vinegar 类
public class Vinegar extends Flavoring { // 醋
public Vinegar(Noodles noodles) {
super(noodles);
}
@Override
public String desc() {
return "多加一份[醋]的 " + noodles.desc();
}
}
2.8 Client 类
public class Client { // 创建 加调味料的 刀削面 和 拉面
public static void main(String[] args) {
// 一份刀削面
Noodles shavedNoodles = new ShavedNoodles();
System.out.println(shavedNoodles.desc());
// 多加 两份辣椒 和 一份食盐
shavedNoodles = new Pepper(shavedNoodles);
shavedNoodles = new Pepper(shavedNoodles);
shavedNoodles = new Salt(shavedNoodles);
System.out.println(shavedNoodles.desc());
// 分隔
System.out.println("===========================================================");
// 一份拉面
Noodles handPulledNoodles = new HandPulledNoodles();
System.out.println(handPulledNoodles.desc());
// 多加 一份辣椒 和 一份醋
handPulledNoodles = new Pepper(handPulledNoodles);
handPulledNoodles = new Vinegar(handPulledNoodles);
System.out.println(handPulledNoodles.desc());
}
}
2.9 Client 类的运行结果
刀削面
多加一份[食盐]的 多加一份[辣椒]的 多加一份[辣椒]的 刀削面
===========================================================
拉面
多加一份[醋]的 多加一份[辣椒]的 拉面
2.10 总结
在本案例中,可以随意组合 两种基本面食 和 三种调味料,这就是 通过创建一个 包装对象(调味料)来 包裹 真实对象(基本面食),并在 保持类方法签名完整性(保持 desc()
的方法签名完整)的前提下,给对象 添加一些新的职责(给面食调味)。
3 各角色之间的关系
3.1 角色
3.1.1 Component ( 抽象零件 )
该角色负责 定义 基本对象的 接口。在本案例中,Noodles
抽象类扮演该角色。
3.1.2 ConcreteComponent ( 具体零件 )
该角色负责 实现 Component 角色所定义的 接口。在本案例中,ShavedNoodles, HandPulledNoodles
类都在扮演该角色。
3.1.3 Decorator ( 抽象装饰者 )
该角色负责 聚合 Component 角色,并 持有与 Component 角色相同的接口。在本案例中,Flavoring
抽象类扮演该角色。
3.1.4 ConcreteDecorator ( 具体装饰者 )
该角色负责 使用 Decorator 聚合的 Component,来 实现 Decorator 中未实现的 接口。在本案例中,Pepper, Salt, Vinegar
类都在扮演该角色。
3.2 类图
说明:在 Component 角色中,method1(), method2(), method3()
都是抽象的,交给其子类实现。在 Decorator 角色中,不需要实现 method1(), method2(), method3()
,只需要 聚合 Component
即可,这些方法交给 ConcreteDecorator 实现。此外,Decorator 角色的 Component
属性是 protected
的,因为要给子类使用。
4 注意事项
- 接口一致性:尽量保持 装饰类的接口 与 被装饰类的接口 相同。这样,对于客户端而言,无论是装饰之前的对象还是装饰之后的对象,都可以一致对待。这有助于保持代码的透明性和可替换性。
- 轻量级设计:尽量保持 具体装饰类 是一个“轻量级”类,不要将太多的行为放在 具体装饰类 中。装饰类的主要职责是为被装饰类添加新的功能或行为,而 不是承载过多的业务逻辑。相反,应该通过装饰类的组合来实现复杂的功能。
- 使用 聚合 代替 继承:在装饰者模式中,通常使用 聚合 而不是 继承 来关联 装饰类 与 被装饰类。这是因为 聚合 可以提供更好的 灵活性 和 扩展性。
- 减少装饰层次:尽量减少装饰者的数量和层次,避免过度使用装饰者模式。过多的装饰层次会增加系统的 复杂性 和 调试难度。
- 简化调用:可以使用 工厂方法模式 或 建造者模式来创建 装饰者对象,以简化客户端的调用。这有助于降低客户端与装饰者之间的耦合度,并提高系统的可维护性。
5 在源码中的使用
装饰者模式在 Java 的 IO 流中得到了广泛的应用。这种设计模式允许 向一个现有的对象添加新的功能,同时又不改变其结构。在 IO 流中,装饰者模式通过 包装现有的流对象 来 增强其功能,如 增加缓冲。
- 四大角色:
- 抽象零件:
InputStream / OutputStream
和Reader / Writer
抽象类。 - 具体零件:直接继承自
InputStream / OutputStream
或Reader / Writer
的流,用于 数据的实际读写操作,也称作节点流,如FileInputStream
、FileOutputStream
等。 - 抽象装饰者:
FilterInputStream / FilterOutputStream
或FilterReader / FilterWriter
类,它们 内部持有一个节点流对象的引用。 - 具体装饰者:继承自
FilterInputStream / FilterOutputStream
或FilterReader / FilterWriter
的流,通过调用节点流的方法来实现数据的读写,也称作过滤流,可以添加额外的功能,如BufferedInputStream
流增加的 缓冲 功能。
- 抽象零件:
- 动态扩展功能:装饰者模式允许在运行时动态地为 IO 流添加新的功能,而不需要修改原有的流类。例如,可以通过包装一个
FileInputStream
对象来创建一个BufferedInputStream
对象,从而增加缓冲功能。 - 透明性:对于客户端而言,无论是直接使用节点流还是使用经过装饰的过滤流,它们的 接口都是相同的。这使得客户端可以透明地使用这些流,而无需关心它们是否被装饰过。
如果只观察 InputStream, FileInputStream, FilterInputStream, BufferedInputStream
这四个类,则可以形成如上的类图,可以将其映射到该模式的类图中。
6 优缺点
优点:
- 扩展性强:通过使用不同的 装饰类 以及这些装饰类的 组合,可以创造出很多 不同行为 的组合。这意味着可以在运行时 动态地 给一个对象添加一些额外的职责,而不需要修改这个对象本身。
- 灵活性高:具体构件类与装饰类可以 独立变化,用户可以根据需要增加新的装饰类或取消装饰类,在使用增加和删除装饰类时,不需要修改具体构件类的代码,装饰者模式提供了比 继承 更多的灵活性。
- 符合开闭原则:对扩展开放,对修改关闭。通过装饰者模式可以在不修改原有类的基础上,增加新的功能。
- 透明性:对于使用装饰者模式的客户来说,它可以 透明地 使用装饰后的对象,无需关心对象是如何被装饰的。
缺点:
- 多层装饰时会更复杂:如果多层装饰比较复杂,可能会 影响代码的可读性。特别是对于 装饰类的排列顺序对结果有影响的情况下,需要特别注意。
- 可能会产生很多小的对象:如果过度使用装饰者模式,可能会产生 大量的装饰类,增加了系统的复杂性,特别是在一些复杂的系统中,这些装饰类可能难以理解和维护。
- 如果 装饰者之间 有关联,可能会增加系统的复杂性:在某些情况下,装饰者之间可能会存在 依赖关系,这种依赖关系可能会使系统的设计和理解变得更加复杂。
- 过度使用可能违反单一职责原则:虽然装饰者模式可以提高类的灵活性,但如果 一个类装饰了过多的功能,可能会违反单一职责原则,导致类的职责不够清晰。
7 适用场景
- 为类扩展功能:当需要为一个类添加 额外的功能或职责,但 不想改变其原有的类结构 时,可以使用装饰者模式。这样可以在不修改原有类代码的情况下,通过组合的方式为对象添加新的功能。
- 动态 添加 或 撤销 功能:装饰者模式允许在运行时动态地给对象 添加新的功能 或 撤销之前添加的功能。这意味着可以根据需要,在对象上添加或移除装饰器,从而改变对象的行为。
- 组合多种功能:通过组合不同的装饰器,可以创建出具有 多种功能组合 的对象。这种灵活性使得装饰者模式在需要 高度可配置性的系统 中非常有用。
8 总结
装饰者模式是一种 结构型 设计模式,它通过创建一个 包装对象(即装饰者)来 包裹 真实对象,并在 保持类方法签名完整性 的前提下,给对象 添加一些新的职责。
装饰者模式是一种很好的模式,在设计系统时推荐使用,它在 高度可配置性的系统 中尤为重要。在实现的时候,注意 不要让具体装饰者的功能太过复杂,从而违背单一职责原则;也不要让具体装饰者之间产生依赖,从而使系统变得复杂。