设计模式之:装饰器模式
文章目录
- 什么是装饰器模式?
- 核心思想
- 生活中的装饰器模式
- 模式结构
- 基础示例:咖啡店系统
- 1. 组件接口
- 2. 具体组件
- 3. 装饰器抽象类
- 4. 具体装饰器
- 5. 客户端使用
- 完整示例:文本处理系统
- 1. 文本组件接口
- 2. 具体文本组件
- 3. 文本装饰器抽象类
- 4. 具体文本装饰器
- 5. 文本处理器客户端
- 实际应用示例:IO流系统
- 装饰器模式的优点
- 1. 灵活性高
- 2. 符合开闭原则
- 3. 避免类爆炸
- 装饰器模式的缺点
- 1. 增加系统复杂度
- 2. 调试困难
- 适用场景
- 最佳实践
- 1. 保持接口一致性
- 2. 保持装饰器的简单性
- 3. 注意装饰顺序
- 装饰器模式 vs 继承
- 总结
什么是装饰器模式?
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
核心思想
装饰器模式的核心思想是:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
生活中的装饰器模式
想象一下我们去咖啡店点咖啡:
- 首先点一杯基础咖啡(被装饰对象)
- 然后可以添加牛奶(装饰器)
- 再加糖(另一个装饰器)
- 再加奶油(又一个装饰器)
每个装饰器都在基础咖啡上添加新的特性,但咖啡的本质没有改变。
模式结构
装饰器模式包含四个核心角色:
- 组件接口(Component):定义对象的接口,可以动态地给这些对象添加职责
- 具体组件(ConcreteComponent):定义具体的对象,可以给这个对象添加一些职责
- 装饰器抽象类(Decorator):继承组件接口,并包含一个组件对象的引用
- 具体装饰器(ConcreteDecorator):向组件添加具体的职责
基础示例:咖啡店系统
1. 组件接口
/*** 饮料接口 - 组件接口* 定义所有饮料的共同操作*/
public interface Beverage {/*** 获取饮料描述*/String getDescription();/*** 计算成本*/double cost();/*** 获取完整信息*/default String getFullInfo() {return String.format("%s - ¥%.2f", getDescription(), cost());}
}
2. 具体组件
/*** 浓缩咖啡 - 具体组件*/
public class Espresso implements Beverage {@Overridepublic String getDescription() {return "浓缩咖啡";}@Overridepublic double cost() {return 25.0;}
}/*** 美式咖啡 - 具体组件*/
public class Americano implements Beverage {@Overridepublic String getDescription() {return "美式咖啡";}@Overridepublic double cost() {return 20.0;}
}/*** 拿铁咖啡 - 具体组件*/
public class Latte implements Beverage {@Overridepublic String getDescription() {return "拿铁咖啡";}@Overridepublic double cost() {return 30.0;}
}
3. 装饰器抽象类
/*** 调料装饰器抽象类 - 装饰器* 所有具体装饰器的父类*/
public abstract class CondimentDecorator implements Beverage {// 持有被装饰对象的引用protected Beverage beverage;public CondimentDecorator(Beverage beverage) {this.beverage = beverage;}@Overridepublic abstract String getDescription();@Overridepublic double cost() {return beverage.cost();}
}
4. 具体装饰器
/*** 牛奶装饰器*/
public class MilkDecorator extends CondimentDecorator {public MilkDecorator(Beverage beverage) {super(beverage);}@Overridepublic String getDescription() {return beverage.getDescription() + " + 牛奶";}@Overridepublic double cost() {return beverage.cost() + 5.0;}
}/*** 糖装饰器*/
public class SugarDecorator extends CondimentDecorator {public SugarDecorator(Beverage beverage) {super(beverage);}@Overridepublic String getDescription() {return beverage.getDescription() + " + 糖";}@Overridepublic double cost() {return beverage.cost() + 2.0;}
}/*** 奶油装饰器*/
public class CreamDecorator extends CondimentDecorator {public CreamDecorator(Beverage beverage) {super(beverage);}@Overridepublic String getDescription() {return beverage.getDescription() + " + 奶油";}@Overridepublic double cost() {return beverage.cost() + 8.0;}
}/*** 香草糖浆装饰器*/
public class VanillaSyrupDecorator extends CondimentDecorator {public VanillaSyrupDecorator(Beverage beverage) {super(beverage);}@Overridepublic String getDescription() {return beverage.getDescription() + " + 香草糖浆";}@Overridepublic double cost() {return beverage.cost() + 6.0;}
}/*** 巧克力装饰器*/
public class ChocolateDecorator extends CondimentDecorator {public ChocolateDecorator(Beverage beverage) {super(beverage);}@Overridepublic String getDescription() {return beverage.getDescription() + " + 巧克力";}@Overridepublic double cost() {return beverage.cost() + 7.0;}
}
5. 客户端使用
/*** 咖啡店客户端*/
public class CoffeeShop {public static void main(String[] args) {System.out.println("=== 咖啡店订单系统 ===\n");// 订单1:浓缩咖啡 + 牛奶 + 糖System.out.println("订单1:");Beverage order1 = new Espresso(); // 浓缩咖啡order1 = new MilkDecorator(order1); // 加牛奶order1 = new SugarDecorator(order1); // 加糖System.out.println(order1.getFullInfo());// 订单2:拿铁 + 奶油 + 巧克力 + 香草糖浆System.out.println("\n订单2:");Beverage order2 = new Latte(); // 拿铁order2 = new CreamDecorator(order2); // 加奶油order2 = new ChocolateDecorator(order2); // 加巧克力order2 = new VanillaSyrupDecorator(order2);// 加香草糖浆System.out.println(order2.getFullInfo());// 订单3:美式咖啡 + 牛奶System.out.println("\n订单3:");Beverage order3 = new Americano(); // 美式咖啡order3 = new MilkDecorator(order3); // 加牛奶System.out.println(order3.getFullInfo());// 显示所有订单总价System.out.println("\n=== 订单汇总 ===");double total = order1.cost() + order2.cost() + order3.cost();System.out.printf("总金额: ¥%.2f%n", total);}
}
完整示例:文本处理系统
让我们通过一个更复杂的文本处理系统来深入理解装饰器模式。
1. 文本组件接口
/*** 文本组件接口*/
public interface TextComponent {/*** 获取文本内容*/String getContent();/*** 获取文本长度*/int getLength();/*** 显示文本信息*/default void display() {System.out.println("内容: " + getContent());System.out.println("长度: " + getLength());}
}
2. 具体文本组件
/*** 基础文本 - 具体组件*/
public class PlainText implements TextComponent {private final String content;public PlainText(String content) {this.content = content;}@Overridepublic String getContent() {return content;}@Overridepublic int getLength() {return content.length();}
}/*** 加密文本 - 具体组件*/
public class EncryptedText implements TextComponent {private final String content;public EncryptedText(String content) {this.content = encrypt(content);}@Overridepublic String getContent() {return decrypt(content);}@Overridepublic int getLength() {return decrypt(content).length();}private String encrypt(String text) {// 简单的加密:字符偏移char[] chars = text.toCharArray();for (int i = 0; i < chars.length; i++) {chars[i] = (char) (chars[i] + 1);}return new String(chars);}private String decrypt(String encrypted) {// 解密:字符偏移还原char[] chars = encrypted.toCharArray();for (int i = 0; i < chars.length; i++) {chars[i] = (char) (chars[i] - 1);}return new String(chars);}
}
3. 文本装饰器抽象类
/*** 文本装饰器抽象类*/
public abstract class TextDecorator implements TextComponent {protected TextComponent textComponent;public TextDecorator(TextComponent textComponent) {this.textComponent = textComponent;}@Overridepublic String getContent() {return textComponent.getContent();}@Overridepublic int getLength() {return textComponent.getLength();}
}
4. 具体文本装饰器
/*** HTML装饰器 - 给文本添加HTML标签*/
public class HtmlDecorator extends TextDecorator {public HtmlDecorator(TextComponent textComponent) {super(textComponent);}@Overridepublic String getContent() {return "<html><body>" + textComponent.getContent() + "</body></html>";}@Overridepublic int getLength() {return getContent().length();}@Overridepublic void display() {System.out.println("HTML格式:");System.out.println(getContent());}
}/*** 大写装饰器 - 将文本转换为大写*/
public class UpperCaseDecorator extends TextDecorator {public UpperCaseDecorator(TextComponent textComponent) {super(textComponent);}@Overridepublic String getContent() {return textComponent.getContent().toUpperCase();}
}/*** 小写装饰器 - 将文本转换为小写*/
public class LowerCaseDecorator extends TextDecorator {public LowerCaseDecorator(TextComponent textComponent) {super(textComponent);}@Overridepublic String getContent() {return textComponent.getContent().toLowerCase();}
}/*** 压缩装饰器 - 移除多余空格*/
public class CompressDecorator extends TextDecorator {public CompressDecorator(TextComponent textComponent) {super(textComponent);}@Overridepublic String getContent() {return textComponent.getContent().replaceAll("\\s+", " ").trim();}@Overridepublic int getLength() {return getContent().length();}
}/*** 前缀装饰器 - 添加前缀*/
public class PrefixDecorator extends TextDecorator {private final String prefix;public PrefixDecorator(TextComponent textComponent, String prefix) {super(textComponent);this.prefix = prefix;}@Overridepublic String getContent() {return prefix + textComponent.getContent();}@Overridepublic int getLength() {return getContent().length();}
}/*** 后缀装饰器 - 添加后缀*/
public class SuffixDecorator extends TextDecorator {private final String suffix;public SuffixDecorator(TextComponent textComponent, String suffix) {super(textComponent);this.suffix = suffix;}@Overridepublic String getContent() {return textComponent.getContent() + suffix;}@Overridepublic int getLength() {return getContent().length();}
}/*** 颜色装饰器 - 添加颜色标记*/
public class ColorDecorator extends TextDecorator {private final String color;public ColorDecorator(TextComponent textComponent, String color) {super(textComponent);this.color = color;}@Overridepublic String getContent() {return String.format("[颜色:%s]%s[/颜色]", color, textComponent.getContent());}@Overridepublic int getLength() {// 颜色标记不计入内容长度return textComponent.getLength();}
}/*** 边框装饰器 - 给文本添加边框*/
public class BorderDecorator extends TextDecorator {private final char borderChar;public BorderDecorator(TextComponent textComponent, char borderChar) {super(textComponent);this.borderChar = borderChar;}@Overridepublic String getContent() {String content = textComponent.getContent();String borderLine = String.valueOf(borderChar).repeat(content.length() + 4);return borderLine + "\n" +borderChar + " " + content + " " + borderChar + "\n" +borderLine;}@Overridepublic int getLength() {return getContent().length();}
}
5. 文本处理器客户端
/*** 文本处理器客户端*/
public class TextProcessor {public static void main(String[] args) {System.out.println("=== 文本处理系统 ===\n");// 示例1:基础文本处理demonstrateBasicTextProcessing();// 示例2:格式化文本demonstrateTextFormatting();// 示例3:加密文本处理demonstrateEncryptedText();// 示例4:复杂装饰组合demonstrateComplexDecoration();}/*** 演示基础文本处理*/private static void demonstrateBasicTextProcessing() {System.out.println("1. 基础文本处理演示:");System.out.println("-".repeat(40));// 创建基础文本TextComponent text = new PlainText(" Hello, Decorator Pattern! ");System.out.println("原始文本:");text.display();// 添加压缩装饰器text = new CompressDecorator(text);System.out.println("\n压缩后:");text.display();// 添加大写装饰器text = new UpperCaseDecorator(text);System.out.println("\n大写后:");text.display();// 添加前缀后缀text = new PrefixDecorator(text, ">>> ");text = new SuffixDecorator(text, " <<<");System.out.println("\n添加前后缀:");text.display();}/*** 演示文本格式化*/private static void demonstrateTextFormatting() {System.out.println("\n2. 文本格式化演示:");System.out.println("-".repeat(40));TextComponent text = new PlainText("装饰器模式很实用");// 添加颜色和边框text = new ColorDecorator(text, "红色");text = new BorderDecorator(text, '*');System.out.println("格式化文本:");System.out.println(text.getContent());}/*** 演示加密文本处理*/private static void demonstrateEncryptedText() {System.out.println("\n3. 加密文本处理演示:");System.out.println("-".repeat(40));// 创建加密文本TextComponent secretText = new EncryptedText("机密信息:项目预算100万");System.out.println("加密内容: " + secretText.getContent());System.out.println("解密后长度: " + secretText.getLength());// 对加密文本进行装饰secretText = new UpperCaseDecorator(secretText);secretText = new BorderDecorator(secretText, '#');System.out.println("\n装饰后的加密文本:");System.out.println(secretText.getContent());}/*** 演示复杂装饰组合*/private static void demonstrateComplexDecoration() {System.out.println("\n4. 复杂装饰组合演示:");System.out.println("-".repeat(40));// 创建复杂的文本处理流水线TextComponent text = new PlainText(" Welcome to Java Design Patterns ");System.out.println("原始文本:");text.display();// 构建处理流水线text = new CompressDecorator(text); // 压缩空格text = new UpperCaseDecorator(text); // 转大写text = new PrefixDecorator(text, "🚀 "); // 添加前缀text = new SuffixDecorator(text, " 🎯"); // 添加后缀text = new ColorDecorator(text, "蓝色"); // 添加颜色text = new BorderDecorator(text, '═'); // 添加边框System.out.println("\n经过完整处理流水线:");System.out.println(text.getContent());System.out.println("最终长度: " + text.getLength());}
}
实际应用示例:IO流系统
Java的IO流系统是装饰器模式的经典应用:
import java.io.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;/*** Java IO流装饰器模式示例*/
public class IOStreamExample {public static void main(String[] args) {System.out.println("=== Java IO流装饰器模式示例 ===\n");// 示例1:基础文件读写demonstrateBasicFileIO();// 示例2:缓冲流装饰器demonstrateBufferedStream();// 示例3:数据流装饰器demonstrateDataStream();// 示例4:压缩流装饰器demonstrateCompressionStream();}/*** 演示基础文件读写*/private static void demonstrateBasicFileIO() {System.out.println("1. 基础文件IO:");try {// 文件输出流 - 具体组件FileOutputStream fos = new FileOutputStream("test.txt");// 使用装饰器添加功能BufferedOutputStream bos = new BufferedOutputStream(fos);PrintStream ps = new PrintStream(bos);ps.println("Hello, Decorator Pattern!");ps.println("This is a test message.");ps.close();System.out.println("文件写入完成");} catch (IOException e) {e.printStackTrace();}}/*** 演示缓冲流装饰器*/private static void demonstrateBufferedStream() {System.out.println("\n2. 缓冲流装饰器:");try {// 读取文件:FileInputStream -> BufferedInputStream -> DataInputStreamFileInputStream fis = new FileInputStream("test.txt");BufferedInputStream bis = new BufferedInputStream(fis);System.out.println("读取文件内容:");int data;while ((data = bis.read()) != -1) {System.out.print((char) data);}bis.close();System.out.println("\n文件读取完成");} catch (IOException e) {e.printStackTrace();}}/*** 演示自定义装饰器*/public static void demonstrateDataStream() {System.out.println("\n3. 数据流装饰器:");try {// 写入数据FileOutputStream fos = new FileOutputStream("data.bin");BufferedOutputStream bos = new BufferedOutputStream(fos);DataOutputStream dos = new DataOutputStream(bos);dos.writeInt(123);dos.writeUTF("装饰器模式");dos.writeDouble(3.14);dos.close();System.out.println("数据写入完成");// 读取数据FileInputStream fis = new FileInputStream("data.bin");BufferedInputStream bis = new BufferedInputStream(fis);DataInputStream dis = new DataInputStream(bis);System.out.println("读取数据:");System.out.println("Int: " + dis.readInt());System.out.println("String: " + dis.readUTF());System.out.println("Double: " + dis.readDouble());dis.close();} catch (IOException e) {e.printStackTrace();}}/*** 演示压缩流装饰器*/public static void demonstrateCompressionStream() {System.out.println("\n4. 压缩流装饰器:");try {// 压缩写入FileOutputStream fos = new FileOutputStream("compressed.gz");GZIPOutputStream gzos = new GZIPOutputStream(fos);BufferedOutputStream bos = new BufferedOutputStream(gzos);String text = "这是一个被压缩的文本内容,使用GZIP装饰器进行压缩处理。";bos.write(text.getBytes());bos.close();System.out.println("压缩文件写入完成");} catch (IOException e) {e.printStackTrace();}}
}
装饰器模式的优点
1. 灵活性高
// 可以动态组合功能
Beverage coffee = new Espresso();
coffee = new MilkDecorator(coffee); // 随时添加牛奶
coffee = new SugarDecorator(coffee); // 随时添加糖
// 不需要修改原有类
2. 符合开闭原则
// 对扩展开放,对修改关闭
// 新增装饰器不需要修改现有代码
public class NewCondimentDecorator extends CondimentDecorator {// 新增装饰器,不影响其他类
}
3. 避免类爆炸
// 不使用装饰器模式需要:
// EspressoWithMilk, EspressoWithSugar, EspressoWithMilkAndSugar...
// 使用装饰器模式只需要:Espresso + 各种装饰器组合
装饰器模式的缺点
1. 增加系统复杂度
// 多层装饰可能使代码难以理解
TextComponent text = new BorderDecorator(new ColorDecorator(new UpperCaseDecorator(new PlainText("hello")), "red"), '*'
);
2. 调试困难
// 多层包装使得调试时难以确定问题所在
// 需要逐层检查装饰器
适用场景
- 在不影响其他对象的情况下,动态、透明地给单个对象添加职责
- 处理那些可以撤销的职责
- 当不能采用继承的方式对系统进行扩充时
最佳实践
1. 保持接口一致性
public abstract class Decorator implements Component {// 装饰器要实现相同的接口// 这样对客户端来说,装饰器和具体组件没有区别
}
2. 保持装饰器的简单性
public class SimpleDecorator extends Decorator {// 每个装饰器只负责一个单一的功能// 避免一个装饰器做太多事情
}
3. 注意装饰顺序
// 装饰顺序可能影响最终结果
TextComponent text = new PlainText("hello");
text = new UpperCaseDecorator(text); // 先大写
text = new BorderDecorator(text); // 后加边框
// 与反向顺序结果可能不同
装饰器模式 vs 继承
特性 | 继承 | 装饰器模式 |
---|---|---|
扩展方式 | 编译时静态扩展 | 运行时动态扩展 |
灵活性 | 低,继承关系在编译时确定 | 高,可以在运行时组合 |
类数量 | 容易产生类爆炸 | 类数量可控 |
维护性 | 修改父类影响所有子类 | 装饰器之间相互独立 |
总结
装饰器模式就像给对象"穿衣服",可以一层一层地添加功能,而且可以随意搭配。
核心价值:
- 动态添加功能,无需修改原有代码
- 功能组合灵活,避免类爆炸
- 符合开闭原则,易于扩展
使用场景:
- 需要动态、透明地给对象添加功能时
- 需要为对象添加的功能可能经常变化时
- 不能使用继承或继承会导致类爆炸时
简单记忆:
想要新功能,包装一下就行!
就像给咖啡加调料,想加什么就加什么。
掌握装饰器模式,能够让你的代码像搭积木一样灵活组合各种功能,创造出无限可能!