设计模式之七—装饰模式(Decorator Pattern)

一、模式介绍

1.1 定义

装饰模式是一种结构型设计模式,它动态地将责任附加到对象上。装饰模式提供了比继承更有弹性的替代方案,用于扩展对象的功能。

1.2 核心思想

  • 不改变原有对象的情况下,动态地给一个对象添加额外的职责
  • 通过组合而非继承来扩展功能
  • 遵循开闭原则(对扩展开放,对修改关闭)

1.3 适用场景

  1. 需要在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
  2. 需要扩展一个类的功能,但使用继承会导致类的数量急剧增加
  3. 需要为对象添加的功能可能被动态地撤销
  4. 当不能采用继承的方式对系统进行扩充时

二、结构组成

2.1 主要角色

  • Component(抽象构件):定义对象的接口,可以给这些对象动态添加职责
  • ConcreteComponent(具体构件):定义具体的对象,即被装饰者
  • Decorator(抽象装饰类):继承Component,持有一个Component对象的引用,并定义与Component一致的接口
  • ConcreteDecorator(具体装饰类):负责给构件对象添加额外的职责

2.2 UML类图

复制代码
            ┌─────────────────┐
            │    Component    │
            ├─────────────────┤
            │+ operation()    │
            └─────────────────┘
                    △
        ┌───────────┴───────────┐
        │                       │
┌───────────────┐      ┌─────────────────┐
│ConcreteComponent│      │    Decorator    │
├───────────────┤      ├─────────────────┤
│+ operation()  │      │- component      │
└───────────────┘      │+ operation()    │
                └─────────────────┘
                                △
                    ┌───────────┴───────────┐
                    │                       │
            ┌───────────────┐     ┌───────────────┐
            │ConcreteDecoratorA │     │ConcreteDecoratorB │
            ├───────────────┤     ├───────────────┤
            │+ operation()  │     │+ operation()  │
            │+ addedBehavior()│     │+ addedBehavior()│
            └───────────────┘     └───────────────┘

三、Java代码示例

3.1 场景描述:咖啡店点餐系统

咖啡店有不同类型的咖啡(如浓缩咖啡、拿铁),并且可以添加各种调料(如牛奶、摩卡、奶泡)。使用装饰模式动态地为咖啡添加调料。

3.2 完整示例代码

java 复制代码
/**
 * 装饰模式示例:咖啡店点餐系统
 * 模拟不同类型的咖啡和可添加的调料
 */

// 1. 抽象组件:饮料接口
interface Beverage {
    String getDescription();
    double getCost();
}

// 2. 具体组件:浓缩咖啡
class Espresso implements Beverage {
    @Override
    public String getDescription() {
        return "浓缩咖啡";
    }
    
    @Override
    public double getCost() {
        return 1.99;
    }
}

// 3. 具体组件:拿铁咖啡
class Latte implements Beverage {
    @Override
    public String getDescription() {
        return "拿铁咖啡";
    }
    
    @Override
    public double getCost() {
        return 2.49;
    }
}

// 4. 抽象装饰类:调料装饰器
abstract class CondimentDecorator implements Beverage {
    protected Beverage beverage;
    
    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }
    
    @Override
    public abstract String getDescription();
}

// 5. 具体装饰类:牛奶
class Milk extends CondimentDecorator {
    public Milk(Beverage beverage) {
        super(beverage);
    }
    
    @Override
    public String getDescription() {
        return beverage.getDescription() + " + 牛奶";
    }
    
    @Override
    public double getCost() {
        return beverage.getCost() + 0.30;
    }
}

// 6. 具体装饰类:摩卡
class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage);
    }
    
    @Override
    public String getDescription() {
        return beverage.getDescription() + " + 摩卡";
    }
    
    @Override
    public double getCost() {
        return beverage.getCost() + 0.50;
    }
}

// 7. 具体装饰类:奶泡
class Whip extends CondimentDecorator {
    public Whip(Beverage beverage) {
        super(beverage);
    }
    
    @Override
    public String getDescription() {
        return beverage.getDescription() + " + 奶泡";
    }
    
    @Override
    public double getCost() {
        return beverage.getCost() + 0.40;
    }
}

// 8. 客户端代码
public class DecoratorPatternDemo {
    public static void main(String[] args) {
        System.out.println("=== 咖啡店点餐系统 ===");
        System.out.println();
        
        // 1. 点一杯浓缩咖啡,不加调料
        Beverage espresso = new Espresso();
        System.out.println("订单1: " + espresso.getDescription());
        System.out.println("价格: $" + espresso.getCost());
        System.out.println();
        
        // 2. 点一杯拿铁,加双份摩卡和奶泡
        Beverage latte = new Latte();
        latte = new Mocha(latte);  // 第一次装饰:加摩卡
        latte = new Mocha(latte);  // 第二次装饰:再加一份摩卡
        latte = new Whip(latte);   // 第三次装饰:加奶泡
        
        System.out.println("订单2: " + latte.getDescription());
        System.out.println("价格: $" + latte.getCost());
        System.out.println();
        
        // 3. 点一杯浓缩咖啡,加所有调料
        Beverage deluxeCoffee = new Espresso();
        deluxeCoffee = new Milk(deluxeCoffee);
        deluxeCoffee = new Mocha(deluxeCoffee);
        deluxeCoffee = new Whip(deluxeCoffee);
        
        System.out.println("订单3: " + deluxeCoffee.getDescription());
        System.out.println("价格: $" + deluxeCoffee.getCost());
        System.out.println();
        
        // 4. 展示装饰过程的动态性
        System.out.println("=== 动态添加调料演示 ===");
        Beverage dynamicCoffee = new Latte();
        System.out.println("初始: " + dynamicCoffee.getDescription() + ", 价格: $" + dynamicCoffee.getCost());
        
        dynamicCoffee = new Milk(dynamicCoffee);
        System.out.println("加牛奶后: " + dynamicCoffee.getDescription() + ", 价格: $" + dynamicCoffee.getCost());
        
        dynamicCoffee = new Mocha(dynamicCoffee);
        System.out.println("加摩卡后: " + dynamicCoffee.getDescription() + ", 价格: $" + dynamicCoffee.getCost());
    }
}

3.3 输出结果

复制代码
=== 咖啡店点餐系统 ===

订单1: 浓缩咖啡
价格: $1.99

订单2: 拿铁咖啡 + 摩卡 + 摩卡 + 奶泡
价格: $3.89

订单3: 浓缩咖啡 + 牛奶 + 摩卡 + 奶泡
价格: $3.19

=== 动态添加调料演示 ===
初始: 拿铁咖啡, 价格: $2.49
加牛奶后: 拿铁咖啡 + 牛奶, 价格: $2.79
加摩卡后: 拿铁咖啡 + 牛奶 + 摩卡, 价格: $3.29

四、Java I/O中的装饰模式应用

Java I/O库是装饰模式的经典应用。以下是一个简化的示例:

java 复制代码
import java.io.*;

/**
 * Java I/O中的装饰模式示例
 */
public class JavaIODecoratorExample {
    public static void main(String[] args) {
        String filePath = "test.txt";
        
        try {
            // 创建基础文件输出流
            FileOutputStream fos = new FileOutputStream(filePath);
            
            // 使用装饰器增强功能
            // BufferedOutputStream 装饰 FileOutputStream,添加缓冲功能
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            
            // DataOutputStream 装饰 BufferedOutputStream,添加基本数据类型写入功能
            DataOutputStream dos = new DataOutputStream(bos);
            
            // 写入数据
            dos.writeUTF("Hello, Decorator Pattern!");
            dos.writeInt(123);
            dos.writeDouble(3.14);
            
            dos.close();
            
            System.out.println("数据写入完成");
            
            // 读取数据:同样使用装饰模式
            FileInputStream fis = new FileInputStream(filePath);
            BufferedInputStream bis = new BufferedInputStream(fis);
            DataInputStream dis = new DataInputStream(bis);
            
            System.out.println("读取数据:");
            System.out.println("字符串: " + dis.readUTF());
            System.out.println("整数: " + dis.readInt());
            System.out.println("浮点数: " + dis.readDouble());
            
            dis.close();
            
            // 清理测试文件
            new File(filePath).delete();
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

五、装饰模式 vs 继承

5.1 对比表格

特性 装饰模式 继承
扩展方式 动态扩展 静态扩展
灵活性 高,可运行时动态添加/移除功能 低,编译时确定
类数量 装饰类和组件类独立,组合使用 需要为每个组合创建子类
代码复用 装饰器可复用 父类功能可复用
设计原则 符合开闭原则 可能违反开闭原则

5.2 使用继承的问题

如果使用继承来实现咖啡店系统,对于3种咖啡和3种调料,需要创建:

  • 基础咖啡类:3个
  • 加一种调料:3 × 3 = 9个类
  • 加两种调料:3 × 3 = 9个类(排列组合)
  • 加三种调料:3 × 1 = 3个类
  • 总计:24个类(类爆炸问题)

使用装饰模式只需要:3个具体咖啡类 + 3个装饰类 = 6个类

六、装饰模式优缺点

6.1 优点

  1. 灵活性高:比继承更灵活,可以动态添加或撤销功能
  2. 避免类爆炸:通过组合少量类,可以创建多种功能组合
  3. 符合开闭原则:无需修改原有代码即可扩展新功能
  4. 职责清晰:每个装饰类只关注自己的功能

6.2 缺点

  1. 增加系统复杂度:会产生许多小对象,增加理解难度
  2. 调试困难:多层装饰使得调试过程复杂
  3. 使用限制:对于不支持组合的对象无法使用装饰模式
  4. 初始化复杂:创建高度装饰的对象需要多层包装

七、实际应用场景

7.1 Java中的应用

  • Java I/O流BufferedReader, BufferedWriter
  • Java GUI:Swing组件边框
  • Java CollectionsCollections.unmodifiableList()
  • Java Web :Servlet API中的HttpServletRequestWrapper

7.2 Spring框架中的应用

  • Spring Security:过滤器链中的各个安全过滤器
  • Spring WebHandlerInterceptor的装饰
  • Spring AOP:通过代理模式实现类似装饰的功能

7.3 实际业务场景

  1. 电商促销系统:基础价格 + 各种促销活动(满减、折扣、优惠券)
  2. 权限控制系统:基础权限 + 各种附加权限
  3. 日志系统:基础日志 + 各种格式化、过滤、输出方式
  4. 消息通知系统:基础通知 + 各种渠道(短信、邮件、推送)

八、扩展:透明装饰 vs 半透明装饰

8.1 透明装饰模式

java 复制代码
// 所有装饰类都继承自同一抽象类/接口
// 客户端可以一致地使用所有对象
Beverage coffee = new Espresso();
coffee = new Milk(coffee);  // 透明装饰,Milk也是Beverage类型
coffee.getDescription();    // 可以调用Beverage的所有方法

8.2 半透明装饰模式

java 复制代码
// 装饰类可能添加新方法,需要客户端知道具体装饰类型
Beverage coffee = new Espresso();
Milk milkCoffee = new Milk(coffee);  // 需要明确声明为Milk类型
// milkCoffee.getDescription();      // 可以调用Beverage方法
// milkCoffee.milkSpecificMethod();  // 还可以调用Milk特有的方法

九、总结

装饰模式提供了一种灵活的替代继承的方式,通过组合对象来扩展功能。它特别适用于以下情况:

  • 需要动态、透明地给对象添加职责
  • 需要避免使用继承导致的类爆炸问题
  • 需要遵循开闭原则,在不修改现有代码的情况下扩展功能

在Java中,装饰模式被广泛应用于I/O流处理、GUI组件装饰等场景。正确使用装饰模式可以提高代码的灵活性和可维护性,但也需要注意避免过度使用导致的系统复杂性增加。

相关推荐
rannn_1112 小时前
【Javaweb学习|Day11】SpringBoot原理|配置优先级、Bean的管理、原理及源码分析
java·spring boot·后端·学习·javaweb
马猴烧酒.2 小时前
智能协图云图库学习笔记day5
java·jvm·spring boot·笔记·学习·mvc
2501_933513042 小时前
Java后端开发者的AGI时代学习与职业路径策略
java·学习·agi
lixin5565562 小时前
基于迁移学习的图像分类增强器
java·人工智能·pytorch·python·深度学习·语言模型
计算机学姐2 小时前
基于SpringBoot的校园跑腿系统【数据可视化统计+原创精品】
java·vue.js·spring boot·后端·mysql·信息可视化·echarts
va学弟2 小时前
Java 网络通信编程(1):服务器多任务连接+广播消息实现
java·运维·服务器
独自破碎E3 小时前
【双指针+字符串】字符串变形
android·java
weixin_462446234 小时前
一键安装 Hadoop 3.3.6 自动化脚本详解 |(含 JAVA_HOME 自动配置)
java·hadoop·自动化
张柏慈10 小时前
Java性能优化:实战技巧与案例解析
java