装饰器模式 (Decorator Pattern)
概述
装饰器模式是一种结构型设计模式,它动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
意图
- 动态地给一个对象添加一些额外的职责
- 就增加功能来说,装饰器模式相比生成子类更为灵活
适用场景
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
- 处理那些可以撤销的职责
- 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数量呈爆炸性增长
- 当需要对已有对象进行功能增强,但又不想通过继承来实现时
结构
┌─────────────┐ ┌─────────────┐
│ Component │<─────────│ Client │
├─────────────┤ ├─────────────┤
│ + operation()│ │ │
└─────────────┘ └─────────────┘
▲
│
┌─────────────┐ ┌─────────────┐
│ConcreteComponent│ │ Decorator │
├─────────────┤ ├─────────────┤
│ + operation()│ │ - component │
└─────────────┘ │ + operation()│
└─────────────┘
▲
│
┌─────────────┐ ┌─────────────┐
│ConcreteDecoratorA│ │ConcreteDecoratorB│
├─────────────┤ ├─────────────┤
│ + operation()│ │ + operation()│
│ + addedBehavior()│ │ + addedState() │
└─────────────┘ └─────────────┘
参与者
- Component:定义一个对象接口,可以给这些对象动态地添加职责
- ConcreteComponent:定义一个具体的对象,也可以给这个对象添加一些职责
- Decorator:维持一个指向Component对象的引用,并定义一个与Component接口一致的接口
- ConcreteDecorator:向组件添加职责
示例代码
下面是一个完整的装饰器模式示例,以咖啡店为例:
java
// Component - 组件接口
public interface Beverage {
String getDescription();
double cost();
}
// ConcreteComponent - 具体组件
public class Espresso implements Beverage {
@Override
public String getDescription() {
return "浓缩咖啡";
}
@Override
public double cost() {
return 1.99;
}
}
// ConcreteComponent - 具体组件
public class HouseBlend implements Beverage {
@Override
public String getDescription() {
return "综合咖啡";
}
@Override
public double cost() {
return 0.89;
}
}
// Decorator - 装饰器抽象类
public abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
public abstract String getDescription();
}
// ConcreteDecorator - 具体装饰器
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 摩卡";
}
@Override
public double cost() {
return beverage.cost() + 0.20;
}
}
// ConcreteDecorator - 具体装饰器
public class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 豆浆";
}
@Override
public double cost() {
return beverage.cost() + 0.15;
}
}
// ConcreteDecorator - 具体装饰器
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 奶泡";
}
@Override
public double cost() {
return beverage.cost() + 0.10;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 点一杯浓缩咖啡
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
// 点一杯综合咖啡,加双倍摩卡和奶泡
Beverage beverage2 = new HouseBlend();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
// 点一杯浓缩咖啡,加豆浆和摩卡
Beverage beverage3 = new Espresso();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
System.out.println(beverage3.getDescription() + " $" + beverage3.cost());
}
}
另一个示例 - 文本处理
java
// Component - 组件接口
public interface TextDisplay {
String getText();
void display();
}
// ConcreteComponent - 具体组件
public class PlainText implements TextDisplay {
private String text;
public PlainText(String text) {
this.text = text;
}
@Override
public String getText() {
return text;
}
@Override
public void display() {
System.out.println(text);
}
}
// Decorator - 装饰器抽象类
public abstract class TextDecorator implements TextDisplay {
protected TextDisplay textDisplay;
public TextDecorator(TextDisplay textDisplay) {
this.textDisplay = textDisplay;
}
@Override
public String getText() {
return textDisplay.getText();
}
@Override
public void display() {
textDisplay.display();
}
}
// ConcreteDecorator - 具体装饰器
public class BoldTextDecorator extends TextDecorator {
public BoldTextDecorator(TextDisplay textDisplay) {
super(textDisplay);
}
@Override
public String getText() {
return "**" + textDisplay.getText() + "**";
}
@Override
public void display() {
System.out.println("**" + textDisplay.getText() + "**");
}
}
// ConcreteDecorator - 具体装饰器
public class ItalicTextDecorator extends TextDecorator {
public ItalicTextDecorator(TextDisplay textDisplay) {
super(textDisplay);
}
@Override
public String getText() {
return "*" + textDisplay.getText() + "*";
}
@Override
public void display() {
System.out.println("*" + textDisplay.getText() + "*");
}
}
// ConcreteDecorator - 具体装饰器
public class UnderlineTextDecorator extends TextDecorator {
public UnderlineTextDecorator(TextDisplay textDisplay) {
super(textDisplay);
}
@Override
public String getText() {
return "_" + textDisplay.getText() + "_";
}
@Override
public void display() {
System.out.println("_" + textDisplay.getText() + "_");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建纯文本
TextDisplay plainText = new PlainText("Hello World");
plainText.display();
// 加粗文本
TextDisplay boldText = new BoldTextDecorator(plainText);
boldText.display();
// 斜体文本
TextDisplay italicText = new ItalicTextDecorator(plainText);
italicText.display();
// 加粗斜体文本
TextDisplay boldItalicText = new BoldTextDecorator(new ItalicTextDecorator(plainText));
boldItalicText.display();
// 加粗斜体下划线文本
TextDisplay decoratedText = new UnderlineTextDecorator(
new BoldTextDecorator(
new ItalicTextDecorator(plainText)
)
);
decoratedText.display();
}
}
优缺点
优点
- 装饰器模式与继承关系的目的都是要扩展对象的功能,但是装饰器模式可以提供比继承更多的灵活性
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一个对象,得到功能更为强大的对象
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合"开闭原则"
缺点
- 装饰器模式比继承更加灵活,但也比继承更加复杂
- 装饰器模式会产生许多小对象,这些对象的区别在于它们之间相互连接的方式不同,而不是它们的类或是属性值不同,这将使得许多小对象变得难以理解
- 装饰器模式比继承更容易出错,排错也更困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐
相关模式
- 适配器模式:适配器模式改变对象的接口,而装饰器模式不改变对象的接口,只增加职责
- 组合模式:组合模式可以将装饰器和组件以一致的方式处理
- 策略模式:策略模式改变对象的行为,而装饰器模式增加对象的职责
- 外观模式:外观模式提供了一个简化的接口,而装饰器模式提供了增强的接口
实际应用
- Java中的I/O流类
- Swing中的组件装饰
- Spring框架中的BeanWrapper
- Java中的Collections.synchronizedList()方法
- Web开发中的过滤器链
Java I/O中的装饰器模式
Java的I/O流是装饰器模式的经典应用:
java
// 基本组件
InputStream inputStream = new FileInputStream("test.txt");
// 装饰器1 - 缓冲流
InputStream bufferedStream = new BufferedInputStream(inputStream);
// 装饰器2 - 数据流
InputStream dataStream = new DataInputStream(bufferedStream);
// 装饰器3 - 对象流
ObjectInputStream objectStream = new ObjectInputStream(dataStream);
装饰器模式与继承的区别
- 装饰器模式:动态地添加功能,可以在运行时组合不同的装饰器
- 继承:静态地添加功能,在编译时确定,无法在运行时改变
装饰器模式提供了比继承更大的灵活性,但同时也增加了系统的复杂性。在选择使用装饰器模式还是继承时,需要根据具体的需求来决定。