设计模式之装饰模式
意图
装饰模式 (Decorator) 是一种结构型设计模式,动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵活。
- 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
- 装饰对象包含一个真实对象的引用(reference)。
- 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
- 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
适合场景
- 如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。
- 如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。
结构
结构说明
- 部件 (Component) 声明封装器和被封装对象的公用接口。
- 具体部件 (Concrete Component) 类是被封装对象所属的类。 它定义了基础行为, 但装饰类可以改变这些行为。
- 基础装饰 (Base Decorator) 类拥有一个指向被封装对象的引用成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就可以引用具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象。
- 具体装饰类 (Concrete Decorators) 定义了可动态添加到部件的额外行为。 具体装饰类会重写装饰基类的方法, 并在调用父类方法之前或之后进行额外的行为。
- 客户端 (Client) 可以使用多层装饰来封装部件, 只要它能使用通用接口与所有对象互动即可。
结构代码范式
Component : 定义一个对象接口,可以给这些对象动态地添加职责。
1 2 3
| interface Component { public void operation(); }
|
ConcreteComponent : 实现 Component 定义的接口。
1 2 3 4 5 6
| class ConcreteComponent implements Component { @Override public void operation() { System.out.println("初始行为"); } }
|
Decorator : 装饰抽象类,继承了 Component, 从外类来扩展 Component 类的功能,但对于 Component 来说,是无需知道 Decorator 的存在的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Decorator implements Component { protected Component component;
public Decorator(Component component) { this.component = component; }
@Override public void operation() { component.operation(); } }
|
ConcreteDecorator : 具体的装饰对象,起到给 Component 添加职责的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class ConcreteDecoratorA extends Decorator { private String addedState = "新属性1";
public ConcreteDecoratorA(Component component) { super(component); }
public void operation() { super.operation(); System.out.println("添加属性: " + addedState); } }
class ConcreteDecoratorB extends Decorator { public ConcreteDecoratorB(Component component) { super(component); }
public void operation() { super.operation(); AddedBehavior(); }
public void AddedBehavior() { System.out.println("添加行为"); } }
|
【客户端】
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class DecoratorPattern { public static void main(String[] args) { Component component = new ConcreteComponent(); component.operation();
System.out.println("======================================"); Decorator decoratorA = new ConcreteDecoratorA(component); decoratorA.operation();
System.out.println("======================================"); Decorator decoratorB = new ConcreteDecoratorB(decoratorA); decoratorB.operation(); } }
|
【输出】
1 2 3 4 5 6 7 8
| 初始行为 ====================================== 初始行为 添加属性: 新属性1 ====================================== 初始行为 添加属性: 新属性1 添加行为
|
伪代码
在本例中, 装饰模式能够对敏感数据进行压缩和加密, 从而将数据从使用数据的代码中独立出来。
程序使用一对装饰来封装数据源对象。 这两个封装器都改变了从磁盘读写数据的方式:
- 当数据即将被写入磁盘前, 装饰对数据进行加密和压缩。 在原始类对改变毫无察觉的情况下, 将加密后的受保护数据写入文件。
- 当数据刚从磁盘读出后, 同样通过装饰对数据进行解压和解密。 装饰和数据源类实现同一接口, 从而能在客户端代码中相互替换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| interface DataSource is method writeData(data) method readData():data
class FileDataSource implements DataSource is constructor FileDataSource(filename) { ... }
method writeData(data) is
method readData():data is
class DataSourceDecorator implements DataSource is protected field wrappee: DataSource
constructor DataSourceDecorator(source: DataSource) is wrappee = source
method writeData(data) is wrappee.writeData(data)
method readData():data is return wrappee.readData()
class EncryptionDecorator extends DataSourceDecorator is method writeData(data) is
method readData():data is
class CompressionDecorator extends DataSourceDecorator is method writeData(data) is
method readData():data is
class Application is method dumbUsageExample() is source = new FileDataSource("somefile.dat") source.writeData(salaryRecords)
source = new CompressionDecorator(source) source.writeData(salaryRecords)
source = new EncryptionDecorator(source) source.writeData(salaryRecords)
class SalaryManager is field source: DataSource
constructor SalaryManager(source: DataSource) { ... }
method load() is return source.readData()
method save() is source.writeData(salaryRecords)
class ApplicationConfigurator is method configurationExample() is source = new FileDataSource("salary.dat") if (enabledEncryption) source = new EncryptionDecorator(source) if (enabledCompression) source = new CompressionDecorator(source)
logger = new SalaryManager(source) salary = logger.load()
|
案例
使用示例: 装饰模式在 Java 代码中可谓是标准配置, 尤其是在与流式加载相关的代码中。
Java 核心程序库中有一些关于装饰的示例:
识别方法: 装饰可通过以当前类或对象为参数的创建方法或构造函数来识别。
与其他模式的关系
- 适配器模式可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。
- 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。
- 责任链模式和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。
- 责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。
- 组合模式和装饰的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。
- 装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。
- 但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。
- 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。
- 装饰可让你更改对象的外表, 策略模式则让你能够改变其本质。
- 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。
参考资料