文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

深入浅出设计模式【九、装饰器模式】

一、装饰器模式介绍

装饰器模式的核心思想是在不改变现有对象结构的情况下,动态地、透明地(对客户端而言)为对象添加新的功能

当使用继承来扩展功能时,类的行为在编译时就被静态地确定了。如果需要多种功能的组合,就会导致“子类爆炸”问题(例如,一个 Beverage 类,如果有 Milk, Soy, Mocha, Whip 等配料,通过继承会产生 DarkRoastWithMilkAndMocha, DarkRoastWithSoyAndWhip 等大量子类)。

装饰器模式通过定义一系列包装对象(装饰器)来解决这个问题。每个装饰器都包装原始对象(或其他装饰器),并在保持原始接口的前提下,提供额外的功能。客户端可以递归地包装对象,从而实现功能的灵活组合。

二、核心概念与意图

  1. 核心概念

    • 组件 (Component): 定义一个对象接口,可以动态地给这些对象添加职责。它是被装饰对象和装饰器对象的共同超类或接口。
    • 具体组件 (Concrete Component): 定义了一个具体的对象,即被装饰的原始对象。它实现了 Component 接口。
    • 装饰器 (Decorator): 持有一个 Component 对象的引用,并实现(或继承)Component 接口。它是所有具体装饰器的公共父类(通常是抽象类),定义了装饰器的基本结构。
    • 具体装饰器 (Concrete Decorator): 向组件(被装饰对象)添加具体的职责。每个具体装饰器都包装了一个 Component 对象,并在其操作前后添加自己的行为。
  2. 意图

    • 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
    • 避免由于子类化而导致的类爆炸问题
    • 保持类的单一职责原则,将核心职责与装饰功能分离。

三、适用场景剖析

装饰器模式在以下场景中非常有效:

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责: 当不能或不希望使用继承来扩展功能时(例如,类被标记为 final,或者扩展会导致类层次结构过于复杂)。
  2. 需要动态地添加、撤销或修改对象的功能: 装饰器可以在运行时被添加和移除,提供了极大的灵活性。
  3. 需要大量可组合的功能: 当有大量独立的功能扩展,并且这些功能需要以各种方式组合使用时(如文本格式、IO流处理、UI组件装饰)。
  4. 对扩展开放,对修改关闭 (Open/Closed Principle): 可以通过创建新的装饰器类来扩展系统,而无需修改现有的组件和装饰器代码。

四、UML 类图解析(Mermaid)

以下UML类图清晰地展示了装饰器模式的结构和角色间的关系,特别是其“包装”和“递归组合”的核心机制:

component
Client
«interface»
Component
+operation()
ConcreteComponent
+operation()
Decorator
-component: Component
+operation()
ConcreteDecoratorA
-addedState
+operation()
+addedBehavior()
ConcreteDecoratorB
+operation()
  • Component (组件): 定义了所有组件(被装饰对象和装饰器)的公共接口(如 operation())。客户端只依赖于这个接口。
  • ConcreteComponent (具体组件): 是被装饰的原始对象。它定义了核心的、基本的功能。
  • Decorator (装饰器)模式的核心
    • 持有一个对 Component 对象的引用 (-component: Component)。这个引用可以指向一个 ConcreteComponent,也可以指向另一个 Decorator,从而形成递归的包装链。
    • 实现(或继承)了 Component 接口。这使得 Decorator 及其子类在外观上与 ConcreteComponent 一致,对客户端是透明的。
    • 它的 operation() 方法通常会调用所引用 componentoperation() 方法(即委托),并可能在此前后添加一些额外的行为。
  • ConcreteDecoratorA, ConcreteDecoratorB (具体装饰器)
    • 继承自 Decorator
    • 在重写的 operation() 方法中,除了调用父类的 operation()(即委托给被包装对象),还会添加自己特有的功能(addedBehavior())。
    • 可以添加新的方法,但为了保持“透明性”,通常应谨慎,以免客户端需要依赖具体装饰器类型。
  • Client (客户端): 通过 Component 接口与对象交互。它不知道也不需要知道它处理的是核心对象还是被装饰过的对象。它负责组装装饰链(例如:component = new ConcreteDecoratorB(new ConcreteDecoratorA(new ConcreteComponent())))。

五、各种实现方式及其优缺点

装饰器模式的实现相对标准,但其设计思想比实现方式更重要。

1. 标准实现(接口+抽象类)

即上述UML所描述的方式,这是最经典和推荐的方式。

// 1. Component Interface
public interface DataSource {
    void writeData(String data);
    String readData();
}

// 2. Concrete Component
public class FileDataSource implements DataSource {
    private String filename;
    // ... constructor, file operations ...
    @Override
    public void writeData(String data) {
        System.out.println("Writing " + data + " to file.");
        // Actual file write logic
    }
    @Override
    public String readData() {
        System.out.println("Reading data from file.");
        return "data";
    }
}

// 3. Base Decorator (Abstract class implementing the interface)
public abstract class DataSourceDecorator implements DataSource {
    protected DataSource wrappee; // The component being decorated

    public DataSourceDecorator(DataSource source) {
        this.wrappee = source;
    }

    @Override
    public void writeData(String data) {
        wrappee.writeData(data); // Delegate to the wrappee
    }

    @Override
    public String readData() {
        return wrappee.readData(); // Delegate to the wrappee
    }
}

// 4. Concrete Decorators
public class EncryptionDecorator extends DataSourceDecorator {
    public EncryptionDecorator(DataSource source) {
        super(source);
    }

    @Override
    public void writeData(String data) {
        String encryptedData = "ENCRYPTED(" + data + ")"; // Simulate encryption
        super.writeData(encryptedData); // Delegate with encrypted data
    }

    @Override
    public String readData() {
        String data = super.readData(); // Read the (encrypted) data
        return data.replace("ENCRYPTED(", "").replace(")", ""); // Simulate decryption
    }
}

public class CompressionDecorator extends DataSourceDecorator {
    public CompressionDecorator(DataSource source) {
        super(source);
    }

    @Override
    public void writeData(String data) {
        String compressedData = "COMPRESSED(" + data + ")"; // Simulate compression
        super.writeData(compressedData);
    }

    @Override
    public String readData() {
        String data = super.readData();
        return data.replace("COMPRESSED(", "").replace(")", ""); // Simulate decompression
    }
}

// 5. Client Code
public class Client {
    public static void main(String[] args) {
        // Original component
        DataSource source = new FileDataSource("data.txt");

        // Dynamically add responsibilities
        DataSource encryptedSource = new EncryptionDecorator(source);
        DataSource compressedAndEncryptedSource = new CompressionDecorator(encryptedSource);

        // Client uses the decorated object transparently
        compressedAndEncryptedSource.writeData("Secret Data");
        // Output: Writing COMPRESSED(ENCRYPTED(Secret Data)) to file.

        String result = compressedAndEncryptedSource.readData();
        // Output: Reading data from file.
        // result will be "Secret Data" after decompression and decryption
    }
}
  • 优点
    • 比继承更灵活: 功能可以动态地添加和撤销,支持功能的无限组合。
    • 避免类爆炸: 使用少量装饰器类,通过组合可以创造出大量不同的行为组合。
    • 符合开闭原则和单一职责原则: 可以引入新的装饰器而不修改现有代码;每个装饰器类只负责一个特定的功能。
  • 缺点
    • 设计复杂度增加: 会引入大量小类,使系统变得复杂,难以理解和调试。
    • 初始化配置复杂: 组装装饰链的代码可能变得冗长且难以维护(可通过工厂模式、建造者模式缓解)。
    • 难以维护装饰器的顺序: 如果装饰器的包装顺序对结果有影响,需要客户端小心管理。

六、最佳实践

  1. 保持接口一致性: 装饰器必须与其装饰的对象实现相同的接口(Component)。这是实现“透明性”的关键,确保客户端无法区分原始对象和装饰后的对象。
  2. 装饰器应专注于添加功能: 装饰器的核心职责是“装饰”,它应该委托核心操作给被包装对象,然后添加边缘行为。避免在装饰器中实现核心业务逻辑。
  3. 使用抽象基类装饰器: 定义一个抽象的 Decorator 基类(如 DataSourceDecorator)来实现接口并将操作委托给被包装对象。这简化了具体装饰器的实现,它们只需重写需要增强的方法。
  4. 谨慎添加新方法: 在装饰器中添加新的公共方法会破坏透明性,因为客户端需要向下转型为具体装饰器类型才能使用它们。如果必须添加新行为,考虑是否应该使用策略模式等其他模式。
  5. 与代理模式区分
    • 装饰器模式目的是增强功能。控制是分散的,由一系列装饰器共同完成。客户端通常负责组装装饰链。
    • 代理模式目的是控制访问(如延迟加载、访问控制、日志记录)。代理通常直接管理其服务对象的生命周期,并且代理和目标对象的关系通常在编译时就已经确定(或由代理内部决定),对客户端是隐藏的。
    • 两者在结构上非常相似,但意图是区分的核心

七、在开发中的演变和应用

装饰器模式的思想是现代框架中实现可扩展性和横切关注点(Cross-Cutting Concerns)的标准手段:

  1. Java I/O Streams: 这是装饰器模式最经典、最教科书式的应用,如前所述。
  2. Servlet API Wrappers: 在Java Web开发中,HttpServletRequestWrapperHttpServletResponseWrapper 是装饰器模式的典型应用。它们允许开发者编写装饰器来包装原始的请求和响应对象,以实现输入/输出的过滤、修改(如压缩、加密)、记录日志等功能,而无需修改原始Servlet代码。Servlet Filter 链就是基于此构建的。
  3. 面向切面编程 (AOP): AOP框架(如Spring AOP)的本质可以看作是在运行时动态创建代理/装饰器,将切面(如事务管理 @Transactional、日志 @Logging)织入到目标方法的前后。这是一种更高级、更自动化的装饰器应用。
  4. GUI工具包: 为可视组件添加边框、滚动条、工具栏等,通常使用装饰器模式实现。

八、真实开发案例(Java语言内部、知名开源框架、工具)

  1. Java I/O (java.io package)

    • ComponentInputStream, OutputStream, Reader, Writer (抽象类)。
    • ConcreteComponentFileInputStream, FileOutputStream, FileReader, FileWriter, ByteArrayInputStream等。
    • DecoratorFilterInputStream, FilterOutputStream, FilterReader, FilterWriter (抽象类)。
    • ConcreteDecorator
      • BufferedInputStream, BufferedOutputStream (添加缓冲功能)。
      • DataInputStream, DataOutputStream (添加读写基本数据类型的功能)。
      • GZIPInputStream, GZIPOutputStream (添加压缩/解压功能)。
      • InputStreamReader, OutputStreamWriter (桥接字节流到字符流,虽是适配器,但也具装饰性)。
    • 客户端使用
      // Transparent chaining of decorators
      InputStream inputStream = new BufferedInputStream( // Decoration: Buffering
                              new GZIPInputStream(      // Decoration: Decompression
                              new FileInputStream("data.gz"))); // Core: File access
      // Client code only deals with the InputStream interface
      int data = inputStream.read();
      
  2. Java Collections - java.util.Collections

    • 方法如 Collections.unmodifiableList(List list), Collections.synchronizedList(List list), Collections.checkedList(List list, Class type)
    • 这些方法返回的包装类对象就是装饰器。它们包装了原始的集合对象,添加了不可修改、线程安全、类型检查等行为,而客户端仍然通过 List 接口与之交互。
  3. Spring Framework - BeanDefinitionDecorator

    • 在Spring的XML配置解析中,用于装饰Bean定义,允许在解析过程中修改或增强Bean的定义信息。
  4. MyBatis - Cache Decorators

    • MyBatis的缓存模块使用了装饰器模式。PerpetualCache 是基本的缓存实现(具体组件),而 LruCache, FifoCache, ScheduledCache, LoggingCache, SynchronizedCache 等都是装饰器,为基本缓存添加了LRU淘汰、FIFO淘汰、定时清空、日志、同步等功能。可以灵活地组合这些装饰器来构建所需的缓存。

九、总结

方面总结
模式类型结构型设计模式
核心意图动态地、透明地给对象添加额外的职责,是继承的替代方案。
关键角色组件(Component), 具体组件(ConcreteComponent), 装饰器(Decorator), 具体装饰器(ConcreteDecorator)
核心机制1. 实现相同接口
2. 包装(组合): 装饰器持有组件引用。
3. 委托与增强: 在调用被包装对象方法前后添加行为。
主要优点1. 极佳的灵活性: 动态添加/撤销功能,避免子类爆炸。
2. 符合开闭原则: 易于扩展新装饰器。
3. 保持单一职责: 功能分解到小类。
主要缺点1. 复杂度增加: 大量小类,调试困难。
2. 初始化复杂: 组装装饰链的代码可能冗长。
适用场景需要动态、透明地扩展对象功能,且不宜使用继承或继承会导致类爆炸的场景(IO处理、UI增强、功能组合、横切关注点)。
最佳实践保持接口透明;使用抽象基类装饰器;优先组合而非继承;与代理模式区分意图。
关系与对比vs. 适配器: 装饰器不改变接口但增强功能;适配器改变接口但不增强功能。
vs. 代理: 装饰器控制分散,目的为增强;代理控制集中,目的为访问控制。结构相似,意图不同。
真实世界应用Java I/O Streams (经典)。Java Collections 的包装方法。Servlet WrappersMyBatis CachingSpring AOP (思想)。

装饰器模式通过巧妙的组合和委托机制,提供了一种极其灵活和符合设计原则的功能扩展方式。它是解决“继承僵化”和“类爆炸”问题的利器,在需要为对象动态添加职责的场景下,是架构师和开发者的首选方案。深入理解装饰器模式,对于编写可维护、可扩展的高质量代码至关重要。

posted @ 2025-08-29 13:12  NeoLshu  阅读(2)  评论(0)    收藏  举报  来源