装饰器模式:设计与实践

20 阅读14分钟

装饰器模式:设计与实践

一、什么是装饰器模式

1. 基本定义

装饰器模式(Decorator Pattern)是一种结构型设计模式,由《设计模式:可复用面向对象软件的基础》(GOF著作)定义为:动态地给一个对象添加一些额外的职责。就增加功能而言,装饰器模式比生成子类更为灵活

该模式通过创建一个包装对象(装饰器)来包裹真实对象,允许在不改变原有对象结构的情况下,动态地为其添加新功能。装饰器与被装饰对象实现相同的接口,使得客户端可以透明地使用被装饰后的对象。

2. 核心思想

装饰器模式的核心在于通过组合而非继承的方式实现功能扩展。它将核心功能与附加功能分离,使每种功能都可以独立变化,同时支持通过嵌套组合的方式叠加多种功能,实现灵活的功能扩展。

二、装饰器模式的特点

1. 动态扩展功能

可以在运行时为对象动态添加或移除功能,无需修改原有对象的代码,符合开闭原则。

2. 接口一致性

装饰器与被装饰对象实现相同的接口,客户端无需区分两者,可以透明地使用装饰后的对象。

3. 功能组合灵活

多个装饰器可以嵌套组合,形成不同的功能组合,满足多样化需求。

4. 单一职责

每个装饰器只负责实现一种附加功能,核心对象只负责实现核心功能,符合单一职责原则。

5. 替代继承

通过组合方式扩展功能,避免了使用继承带来的类爆炸问题,提高了系统的灵活性。

特点说明
动态扩展运行时为对象添加/移除功能,无需修改原有代码
接口一致装饰器与被装饰对象实现相同接口,客户端透明使用
组合灵活多个装饰器可嵌套组合,形成多样化功能组合
单一职责每个装饰器只负责一种附加功能,职责清晰
替代继承通过组合扩展功能,避免类爆炸问题

三、装饰器模式的标准代码实现

1. 模式结构

装饰器模式包含四个核心角色:

  • 抽象组件(Component):定义装饰器和被装饰对象的共同接口
  • 具体组件(ConcreteComponent):实现抽象组件接口,是被装饰的核心对象
  • 抽象装饰器(Decorator):实现抽象组件接口,持有一个抽象组件的引用,定义装饰器的基本结构
  • 具体装饰器(ConcreteDecorator):继承抽象装饰器,实现具体的附加功能

2. 代码实现示例

2.1 抽象组件接口
/**
 * 抽象组件接口
 * 定义核心功能接口
 */
public interface Component {
    void operation();
}
2.2 具体组件实现
/**
 * 具体组件类
 * 实现核心功能
 */
public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("执行核心功能");
    }
}
2.3 抽象装饰器
/**
 * 抽象装饰器
 * 实现组件接口,持有组件引用
 */
public abstract class Decorator implements Component {
    // 持有被装饰的组件对象
    protected Component component;

    // 通过构造函数注入组件
    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        // 调用被装饰组件的方法
        component.operation();
    }
}
2.4 具体装饰器A
/**
 * 具体装饰器A
 * 实现第一种附加功能
 */
public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    // 附加功能A
    private void addFunctionA() {
        System.out.println("添加附加功能A");
    }

    @Override
    public void operation() {
        // 先执行附加功能
        addFunctionA();
        // 再执行被装饰组件的核心功能
        super.operation();
    }
}
2.5 具体装饰器B
/**
 * 具体装饰器B
 * 实现第二种附加功能
 */
public class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    // 附加功能B
    private void addFunctionB() {
        System.out.println("添加附加功能B");
    }

    @Override
    public void operation() {
        // 先执行被装饰组件的核心功能
        super.operation();
        // 再执行附加功能
        addFunctionB();
    }
}
2.6 客户端使用示例
/**
 * 客户端示例
 * 演示装饰器的使用和组合
 */
public class Client {
    public static void main(String[] args) {
        // 1. 创建核心组件对象
        Component component = new ConcreteComponent();

        // 2. 使用装饰器A装饰核心组件
        Component decoratorA = new ConcreteDecoratorA(component);
        System.out.println("=== 装饰器A效果 ===");
        decoratorA.operation();

        // 3. 使用装饰器B装饰核心组件
        Component decoratorB = new ConcreteDecoratorB(component);
        System.out.println("\n=== 装饰器B效果 ===");
        decoratorB.operation();

        // 4. 组合使用装饰器A和B
        Component decoratorAB = new ConcreteDecoratorB(new ConcreteDecoratorA(component));
        System.out.println("\n=== 装饰器A+B效果 ===");
        decoratorAB.operation();
    }
}

3. 代码实现特点总结

角色核心职责代码特点
抽象组件(Component)定义核心功能接口声明组件的基本操作方法
具体组件(ConcreteComponent)实现核心功能专注于核心业务逻辑,不包含附加功能
抽象装饰器(Decorator)定义装饰器结构实现Component接口,持有Component引用,委托核心操作
具体装饰器(ConcreteDecoratorA/B)实现附加功能继承Decorator,添加特定附加功能,在调用父类方法前后执行

四、支付框架设计中装饰器模式的运用

账单生成器功能扩展为例,说明装饰器模式在支付系统中的具体实现:

1. 场景分析

支付系统需要为商户生成各类账单(日账单、月账单、对账账单等),基础功能是生成账单数据,但不同商户可能需要额外功能:添加水印(防止篡改)、数据加密(敏感信息保护)、数据压缩(减少传输大小)、格式转换(XML/JSON)等。这些功能需要根据商户配置动态启用,使用装饰器模式可以灵活组合这些功能,同时保持核心账单生成逻辑的简洁。

2. 设计实现

2.1 账单生成器接口(抽象组件)
import java.io.OutputStream;

/**
 * 账单生成器接口(抽象组件)
 * 定义账单生成的核心功能
 */
public interface BillGenerator {
    /**
     * 生成账单并写入输出流
     * @param query 账单查询条件
     * @param output 输出流
     */
    void generate(BillQuery query, OutputStream output);
}
2.2 账单查询与基础模型
import java.time.LocalDate;

/**
 * 账单查询条件
 */
public class BillQuery {
    private String merchantId; // 商户ID
    private String billType; // 账单类型:DAY/MONTH/RECONCILIATION
    private LocalDate startDate; // 开始日期
    private LocalDate endDate; // 结束日期

    // getter和setter ...
}
2.3 基础账单生成器(具体组件)
import java.io.OutputStream;
import java.io.PrintWriter;

/**
 * 基础账单生成器(具体组件)
 * 实现账单生成的核心功能
 */
public class BasicBillGenerator implements BillGenerator {
    private final BillRepository billRepository;

    public BasicBillGenerator(BillRepository billRepository) {
        this.billRepository = billRepository;
    }

    @Override
    public void generate(BillQuery query, OutputStream output) {
        // 1. 查询账单数据
        BillData billData = billRepository.queryBillData(
                query.getMerchantId(),
                query.getStartDate(),
                query.getEndDate(),
                query.getBillType()
        );

        // 2. 生成原始账单内容
        try (PrintWriter writer = new PrintWriter(output)) {
            // 写入账单头部
            writer.println("===== " + query.getBillType() + "账单 =====");
            writer.println("商户ID: " + query.getMerchantId());
            writer.println("日期范围: " + query.getStartDate() + " 至 " + query.getEndDate());
            writer.println("===== 交易明细 =====");

            // 写入交易明细
            for (BillItem item : billData.getItems()) {
                writer.printf("%s\t%s\t%s\t%.2f\t%s%n",
                        item.getOrderId(),
                        item.getTradeTime(),
                        item.getTradeType(),
                        item.getAmount(),
                        item.getStatus()
                );
            }

            // 写入账单汇总
            writer.println("===== 汇总信息 =====");
            writer.printf("总交易笔数: %d%n", billData.getTotalCount());
            writer.printf("总交易金额: %.2f%n", billData.getTotalAmount());
            writer.printf("生成时间: %s%n", billData.getGenerateTime());
        }
    }
}
2.4 账单装饰器抽象类
import java.io.OutputStream;

/**
 * 账单装饰器抽象类
 * 定义账单功能扩展的基本结构
 */
public abstract class BillDecorator implements BillGenerator {
    // 持有被装饰的账单生成器
    protected final BillGenerator generator;

    public BillDecorator(BillGenerator generator) {
        this.generator = generator;
    }

    @Override
    public void generate(BillQuery query, OutputStream output) {
        // 委托给被装饰的生成器
        generator.generate(query, output);
    }
}
2.5 具体装饰器实现
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.GZIPOutputStream;

/**
 * 水印装饰器
 * 为账单添加水印,防止篡改
 */
public class WatermarkDecorator extends BillDecorator {
    private final String merchantSecret; // 商户密钥,用于生成水印

    public WatermarkDecorator(BillGenerator generator, String merchantSecret) {
        super(generator);
        this.merchantSecret = merchantSecret;
    }

    @Override
    public void generate(BillQuery query, OutputStream output) {
        // 使用 ByteArrayOutputStream 捕获原始账单内容
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            // 1. 生成原始账单
            super.generate(query, baos);

            // 2. 添加水印
            String originalContent = baos.toString(StandardCharsets.UTF_8.name());
            String watermark = generateWatermark(query, originalContent);
            String contentWithWatermark = originalContent + "\n===== 水印信息 =====" + "\n" + watermark;

            // 3. 写入最终内容
            output.write(contentWithWatermark.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            throw new RuntimeException("添加水印失败", e);
        }
    }

    // 生成水印(基于账单内容和商户密钥)
    private String generateWatermark(BillQuery query, String content) {
        // 实际实现中会使用加密算法生成防篡改水印
        return "WATERMARK_" + query.getMerchantId() + "_" + 
               content.hashCode() + "_" + merchantSecret.substring(0, 8);
    }
}

/**
 * 加密装饰器
 * 对账单中的敏感信息进行加密
 */
public class EncryptDecorator extends BillDecorator {
    private final String encryptKey; // 加密密钥

    public EncryptDecorator(BillGenerator generator, String encryptKey) {
        super(generator);
        this.encryptKey = encryptKey;
    }

    @Override
    public void generate(BillQuery query, OutputStream output) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            // 1. 生成原始账单
            super.generate(query, baos);

            // 2. 加密敏感信息
            String originalContent = baos.toString(StandardCharsets.UTF_8.name());
            String encryptedContent = encryptSensitiveData(originalContent);

            // 3. 写入加密后内容
            output.write(encryptedContent.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new RuntimeException("加密账单失败", e);
        }
    }

    // 加密敏感信息(如银行卡号、身份证号等)
    private String encryptSensitiveData(String content) throws Exception {
        // 实际实现中会识别并加密敏感字段
        SecretKeySpec key = new SecretKeySpec(encryptKey.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);

        byte[] encryptedBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
        return "ENCRYPTED_DATA:" + Base64.getEncoder().encodeToString(encryptedBytes);
    }
}

/**
 * 压缩装饰器
 * 对账单内容进行压缩,减少传输大小
 */
public class CompressDecorator extends BillDecorator {
    public CompressDecorator(BillGenerator generator) {
        super(generator);
    }

    @Override
    public void generate(BillQuery query, OutputStream output) {
        // 使用GZIPOutputStream进行压缩
        try (GZIPOutputStream gzipOutput = new GZIPOutputStream(output)) {
            // 直接将原始账单内容写入压缩流
            super.generate(query, gzipOutput);
        } catch (IOException e) {
            throw new RuntimeException("压缩账单失败", e);
        }
    }
}
2.6 账单服务工厂(组合装饰器)
/**
 * 账单服务工厂
 * 根据商户配置组合不同的装饰器
 */
public class BillServiceFactory {
    private final BillRepository billRepository;
    private final MerchantConfigService configService;

    public BillServiceFactory(BillRepository billRepository, MerchantConfigService configService) {
        this.billRepository = billRepository;
        this.configService = configService;
    }

    /**
     * 根据商户ID创建账单生成器
     */
    public BillGenerator createBillGenerator(String merchantId) {
        // 1. 创建基础账单生成器
        BillGenerator generator = new BasicBillGenerator(billRepository);

        // 2. 获取商户配置
        MerchantConfig config = configService.getConfig(merchantId);

        // 3. 根据配置添加装饰器
        if (config.isWatermarkEnabled()) {
            generator = new WatermarkDecorator(generator, config.getSecretKey());
        }
        if (config.isEncryptionEnabled()) {
            generator = new EncryptDecorator(generator, config.getEncryptKey());
        }
        if (config.isCompressionEnabled()) {
            generator = new CompressDecorator(generator);
        }

        return generator;
    }
}

// 商户配置类
class MerchantConfig {
    private String merchantId;
    private boolean watermarkEnabled;
    private boolean encryptionEnabled;
    private boolean compressionEnabled;
    private String secretKey;
    private String encryptKey;

    // getter和setter省略
}
2.7 客户端使用示例
import java.io.FileOutputStream;
import java.time.LocalDate;

/**
 * 客户端示例
 * 演示如何使用装饰器模式生成账单
 */
public class BillClient {
    public static void main(String[] args) {
        // 初始化依赖
        BillRepository billRepository = new BillRepositoryImpl();
        MerchantConfigService configService = new MerchantConfigServiceImpl();
        BillServiceFactory factory = new BillServiceFactory(billRepository, configService);

        // 获取为商户MCH001配置的账单生成器
        BillGenerator generator = factory.createBillGenerator("MCH001");

        // 创建账单查询条件
        BillQuery query = new BillQuery();
        query.setMerchantId("MCH001");
        query.setBillType("DAY");
        query.setStartDate(LocalDate.of(2023, 10, 1));
        query.setEndDate(LocalDate.of(2023, 10, 1));

        // 生成账单并保存到文件
        try (FileOutputStream fos = new FileOutputStream("MCH001_DAY_BILL.txt")) {
            generator.generate(query, fos);
            System.out.println("账单生成成功");
        } catch (Exception e) {
            System.err.println("账单生成失败: " + e.getMessage());
        }
    }
}

3. 模式价值体现

  • 功能灵活组合:根据商户配置动态组合水印、加密、压缩等功能,满足不同商户的个性化需求
  • 核心逻辑清晰:基础账单生成器专注于数据查询和格式编排,不包含任何扩展功能代码
  • 易于扩展:新增功能(如格式转换)只需添加新的装饰器,无需修改现有代码
  • 职责单一:每个装饰器只负责一项功能(水印/加密/压缩),代码可读性和可维护性高
  • 配置驱动:通过配置决定启用哪些功能,无需重新部署即可变更功能组合

五、开源框架中装饰器模式的运用

Netty框架中的ChannelHandlerChannelPipeline为例,说明装饰器模式在框架级别的应用:

1. 核心实现分析

Netty是一个高性能的网络通信框架,其核心组件ChannelPipeline(通道流水线)和ChannelHandler(通道处理器)广泛使用了装饰器模式,用于对网络数据进行分层处理(解码、编码、日志、加密等)。

1.1 ChannelHandler接口(抽象组件)

ChannelHandler是Netty中处理网络事件的接口,定义了网络数据处理的基本方法:

public interface ChannelHandler {
    // 通道激活事件
    void channelActive(ChannelHandlerContext ctx) throws Exception;
    // 通道读取事件
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
    // 其他事件处理方法...
}
1.2 ChannelHandlerAdapter(抽象装饰器)

ChannelHandlerAdapterChannelHandler的适配器实现,提供了默认空实现,作为装饰器的基类:

public abstract class ChannelHandlerAdapter implements ChannelHandler {
    // 持有上下文引用
    protected ChannelHandlerContext ctx;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 默认实现:将事件传递给下一个处理器
        ctx.fireChannelActive();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 默认实现:将消息传递给下一个处理器
        ctx.fireChannelRead(msg);
    }
    // 其他方法默认实现...
}
1.3 具体装饰器实现

Netty提供了多种ChannelHandler实现(具体装饰器),用于实现不同的网络处理功能:

  • LoggingHandler:记录网络事件日志
  • SslHandler:对网络数据进行SSL加密解密
  • LengthFieldPrepender:为消息添加长度字段
  • StringDecoder:将字节数据解码为字符串

LoggingHandler为例:

public class LoggingHandler extends ChannelHandlerAdapter {
    private final Logger logger;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 前置处理:记录读取到的消息
        logger.info("Received message: {}", msg);
        // 调用父类方法,将消息传递给下一个处理器
        super.channelRead(ctx, msg);
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        // 前置处理:记录要发送的消息
        logger.info("Sending message: {}", msg);
        // 调用父类方法,将消息传递给下一个处理器
        super.write(ctx, msg, promise);
    }
}
1.4 ChannelPipeline(装饰器组合)

ChannelPipeline负责管理ChannelHandler的组合和执行顺序,相当于装饰器模式中的客户端,负责构建处理器链:

// 构建Netty客户端的处理器链
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        // 添加日志处理器
        pipeline.addLast(new LoggingHandler());
        // 添加SSL加密处理器
        pipeline.addLast(new SslHandler(sslContext.newEngine(ch.alloc())));
        // 添加消息解码处理器
        pipeline.addLast(new StringDecoder());
        // 添加业务处理器
        pipeline.addLast(new BusinessHandler());
    }
});

2. 装饰器模式在Netty中的价值

  • 功能分层处理:将网络通信的不同处理阶段(日志、加密、编解码、业务处理)分离为不同的ChannelHandler
  • 灵活组合:通过ChannelPipeline自由组合不同的ChannelHandler,形成定制化的处理流程
  • 可扩展性:用户可以自定义ChannelHandler实现特定功能,无缝集成到现有处理链中
  • 职责清晰:每个ChannelHandler只负责一种处理功能,代码模块化程度高

六、总结

1. 装饰器模式的适用场景

  • 当需要为对象动态添加或移除功能时
  • 当需要组合多种功能,且功能组合方式可能频繁变化时
  • 当使用继承会导致大量子类(类爆炸)时
  • 当需要保持核心功能简洁,将附加功能与核心功能分离时
  • 当需要在不修改原有代码的前提下扩展功能时

2. 装饰器模式与其他模式的区别

  • 与代理模式:两者都通过组合方式扩展对象功能,都实现了与被包装对象相同的接口。但装饰器模式侧重于功能扩展,代理模式侧重于访问控制;装饰器通常是透明的嵌套组合,代理通常是单一的包装。
  • 与适配器模式:两者都通过包装对象改变其接口或功能。但装饰器模式不改变接口,只扩展功能;适配器模式主要用于接口转换,使不兼容的接口可以一起工作。
  • 与继承:两者都可以用于功能扩展。但继承是静态的编译期扩展,装饰器是动态的运行期扩展;继承会导致类层次结构复杂,装饰器通过组合方式更灵活。

3. 支付系统中的实践价值

  • 功能模块化:将支付系统中的附加功能(日志、安全、监控)封装为装饰器,与核心业务逻辑分离
  • 配置驱动:通过配置动态启用或禁用功能,适应不同商户、不同场景的需求
  • 易于扩展:新增功能只需实现新的装饰器,无需修改核心代码,降低变更风险
  • 合规性支持:支付行业的安全合规要求(如敏感信息加密、审计日志)可以通过装饰器统一实现,确保全系统合规

4. 实践建议

  • 保持装饰器的单一职责,每个装饰器只实现一种功能
  • 避免过度使用装饰器,过多的装饰器嵌套会增加系统复杂度和调试难度
  • 装饰器应该是无状态的,或者状态是独立的,避免多个装饰器之间产生依赖
  • 对于核心链路的高频操作,需要考虑装饰器的性能开销,必要时进行性能优化

装饰器模式通过组合方式实现功能的动态扩展,为支付系统中多样化、个性化的功能需求提供了灵活的解决方案,同时保持了系统的简洁性和可维护性,是支付框架设计中不可或缺的设计模式之一。