装饰模式:动态扩展对象功能的优雅设计

装饰模式:动态扩展对象功能的优雅设计

一、模式核心:不修改原类,动态添加功能

在软件开发中,我们经常需要为现有对象添加新功能(如给 "文本框" 添加 "验证功能""日志功能"),但直接修改原类会违反开闭原则。装饰模式(Decorator Pattern) 通过创建一个包装对象(装饰器),在不改变原对象的前提下动态扩展其功能,核心解决:

  • 功能动态组合:可灵活选择不同装饰器组合,实现功能的 "即插即用"
  • 避免继承膨胀:替代多层继承,减少子类数量(如 "带验证的文本框" 无需继承原文本框类)

核心思想与 UML 类图

装饰模式通过让装饰器与被装饰对象实现相同接口,形成递归包装结构,使客户端透明地使用装饰后的对象:

二、核心实现:为文件流添加加密与压缩功能

1. 定义组件接口(抽象功能)

java 复制代码
public interface DataStream {
    void write(String data); // 写入数据
}

2. 实现具体组件(基础功能:普通文件流)

java 复制代码
public class FileStream implements DataStream {
    private String filePath;

    public FileStream(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public void write(String data) {
        System.out.println("写入文件 [" + filePath + "]: " + data);
    }
}

3. 定义装饰器抽象类(维护被装饰对象引用)

java 复制代码
public abstract class StreamDecorator implements DataStream {
    protected DataStream dataStream;

    public StreamDecorator(DataStream dataStream) {
        this.dataStream = dataStream;
    }

    @Override
    public void write(String data) {
        dataStream.write(data); // 委托给被装饰对象
    }
}

4. 实现具体装饰器(新增加密功能)

java 复制代码
public class EncryptionDecorator extends StreamDecorator {
    public EncryptionDecorator(DataStream dataStream) {
        super(dataStream);
    }

    // 加密算法(示例:简单反转字符串)
    private String encrypt(String data) {
        return new StringBuilder(data).reverse().toString();
    }

    @Override
    public void write(String data) {
        String encryptedData = encrypt(data); // 前置加密处理
        super.write(encryptedData); // 调用被装饰对象的写入逻辑
    }
}

5. 实现另一个装饰器(新增压缩功能)

java 复制代码
public class CompressionDecorator extends StreamDecorator {
    public CompressionDecorator(DataStream dataStream) {
        super(dataStream);
    }

    // 压缩算法(示例:简单字符串截断)
    private String compress(String data) {
        return data.length() > 10 ? data.substring(0, 10) + "..." : data;
    }

    @Override
    public void write(String data) {
        String compressedData = compress(data); // 前置压缩处理
        super.write(compressedData);
    }
}

6. 客户端组合装饰器(动态扩展功能)

java 复制代码
public class ClientDemo {
    public static void main(String[] args) {
        // 基础功能:普通文件流
        DataStream simpleStream = new FileStream("data.txt");
        simpleStream.write("Hello, World!"); // 直接写入原始数据

        // 组合加密装饰器
        DataStream encryptedStream = new EncryptionDecorator(simpleStream);
        encryptedStream.write("Secret Message"); // 写入前自动加密

        // 组合加密+压缩装饰器(顺序影响结果)
        DataStream complexStream = new CompressionDecorator(
            new EncryptionDecorator(simpleStream)
        );
        complexStream.write("LongTextThatNeedsCompressionAndEncryption");
    }
}

三、进阶:实现 JDK IO 式的多层装饰器链

1. 支持链式调用的装饰器设计

java 复制代码
public abstract class ChainableDecorator implements DataStream {
    protected DataStream dataStream;

    public ChainableDecorator(DataStream dataStream) {
        this.dataStream = dataStream;
    }

    // 支持连续装饰(返回当前装饰器实例)
    public DataStream decorate(DataStream decorator) {
        return new ChainableDecorator(decorator) {
            @Override
            public void write(String data) {
                // 可自定义装饰顺序(前置/后置)
                data = "[" + data + "]"; // 新增包装逻辑
                super.write(data);
            }
        };
    }
}

// 使用示例:链式装饰
DataStream stream = new FileStream("log.txt");
stream = new EncryptionDecorator(stream).decorate(new CompressionDecorator(stream));

2. 装饰器与 AOP 的结合(方法级增强)

java 复制代码
// 使用Spring AOP实现装饰器逻辑
@Aspect
public class StreamAspect {
    @Around("execution(* com.example.DataStream.write(..))")
    public Object addDecorator(ProceedingJoinPoint joinPoint) throws Throwable {
        String data = (String) joinPoint.getArgs()[0];
        // 前置增强:加密
        String encryptedData = encrypt(data);
        // 调用原始方法
        joinPoint.proceed(new Object[]{encryptedData});
        // 后置增强:记录日志
        logWrite(encryptedData);
        return null;
    }
}

3. 可视化装饰流程(Mermaid 流程图)

graph LR A[客户端请求] --> B[装饰器A处理] B --> C[被装饰对象处理] C --> D[装饰器B处理] D --> E[返回结果]

四、框架与源码中的装饰模式实践

1. Java IO 流(典型装饰模式应用)

  • 核心类:

    • 抽象组件:InputStream/OutputStream
    • 具体组件:FileInputStream/FileOutputStream
    • 装饰器:BufferedInputStream(添加缓冲)、DataInputStream(添加数据类型支持)
  • 使用示例:

    java 复制代码
    InputStream input = new BufferedInputStream(
        new DataInputStream(
            new FileInputStream("file.txt")
        )
    );

2. Servlet Filter(请求响应装饰)

  • Filter通过FilterChain形成装饰链,对请求和响应进行增强
java 复制代码
public class EncodingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        request = new EncodedServletRequest((HttpServletRequest) request); // 装饰请求
        chain.doFilter(request, response); // 传递给下一个Filter
    }
}

3. MyBatis 插件(Interceptor)

  • 通过拦截器装饰ExecutorStatementHandler等对象,添加分页、性能监控等功能
java 复制代码
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class PerformanceInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = invocation.proceed(); // 调用原始方法
        long end = System.currentTimeMillis();
        logPerformance(end - start); // 后置装饰:记录执行时间
        return result;
    }
}

五、避坑指南:正确使用装饰模式的 3 个要点

1. 确保装饰器与组件接口一致

  • ❌ 反模式:装饰器未实现与组件相同的接口,导致客户端无法透明调用
  • ✅ 最佳实践:装饰器必须继承 / 实现组件接口,并重写所有方法

2. 控制装饰链长度(避免性能损耗)

  • 过长的装饰链会增加方法调用层级,建议:
    • 对装饰器进行分组(如 "输入处理组""输出处理组")
    • 使用CompositeDecorator合并同类装饰器

3. 避免装饰顺序影响逻辑

  • 装饰器的执行顺序可能影响结果(如先加密后压缩 vs 先压缩后加密),需:
    • 在文档中明确装饰器顺序规范
    • 提供工厂类统一管理装饰链组装

4. 反模式:滥用装饰器替代继承

  • 当功能扩展是 "is-a" 关系时(如 "鸟类" 是 "动物类"),应使用继承而非装饰器

六、总结:何时该用装饰模式?

适用场景 核心特征 典型案例
动态添加功能 功能可灵活组合,且运行时动态决定 GUI 组件皮肤切换、游戏角色技能升级
避免类爆炸 功能组合可能导致子类数量激增(如 n 个功能→2ⁿ个子类) 日志系统(DEBUG/INFO/ERROR 级别组合)
遵循开闭原则 不修改原代码,通过新增装饰器扩展功能 框架插件机制、遗留系统功能增强

装饰模式通过 "包装对象 + 功能委托" 的设计,使系统在不破坏原有结构的前提下实现功能的动态扩展,是应对 "需求变化" 的核心设计模式之一。下一篇我们将深入探讨适配器模式,解析如何让不兼容的接口协同工作,敬请期待!

扩展思考:装饰模式 vs 代理模式

两者都通过包装对象实现功能增强,但核心差异在于:

模式 目标 持有对象关系 典型场景
装饰模式 增强功能(附加职责) 装饰器与组件是 "is-a" 关系 添加日志、加密、压缩
代理模式 控制访问(替代职责) 代理与目标是 "proxy-for" 关系 远程调用、权限控制、懒加载

理解这种差异,能帮助我们在设计时选择更合适的模式来解决实际问题。

相关推荐
中国lanwp4 分钟前
Spring Boot 版本与对应 JDK 版本兼容性
java·开发语言·spring boot
懒虫虫~8 分钟前
Spring源码中关于抽象方法且是个空实现这样设计的思考
java·后端·spring
码银10 分钟前
【Java】接口interface学习
java·开发语言·学习
雷渊17 分钟前
DDD的分层架构是怎么样的?
后端
DKPT22 分钟前
重构之去除多余的if-else
java·开发语言·笔记·学习·面试
蓝黑202024 分钟前
Java如何在遍历集合时删除特定元素
java
会有猫24 分钟前
阿里云OSS挂载到Linux
后端
雷渊28 分钟前
聊一聊贫血模型和充血模型区别
后端
掘金詹姆斯28 分钟前
如何基于状态机对订单状态实现统一管理?
java·状态机
雨月琉琉30 分钟前
Jenkins设置中文显示
java·servlet·jenkins