Skip to content

装饰器模式

基础概念

什么是装饰器模式?

答案: 装饰器模式(Decorator Pattern)动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更为灵活。

使用场景

  • 需要扩展一个类的功能
  • 动态地给对象添加功能
  • 需要为一批类增加功能

优点

  • 比继承更灵活
  • 符合开闭原则
  • 可以动态组合

缺点

  • 会产生很多小对象
  • 多层装饰比较复杂

实现方式

基本实现?

答案:

java
// 组件接口
public interface Component {
    void operation();
}

// 具体组件
public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("基本功能");
    }
}

// 装饰器抽象类
public abstract class Decorator implements Component {
    protected Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
    }
}

// 具体装饰器A
public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        addedFunction();
    }

    private void addedFunction() {
        System.out.println("装饰器A的附加功能");
    }
}

// 具体装饰器B
public class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        addedFunction();
    }

    private void addedFunction() {
        System.out.println("装饰器B的附加功能");
    }
}

// 使用
Component component = new ConcreteComponent();
component = new ConcreteDecoratorA(component);
component = new ConcreteDecoratorB(component);
component.operation();
// 输出:
// 基本功能
// 装饰器A的附加功能
// 装饰器B的附加功能

实际应用

咖啡订单系统

答案:

java
// 饮料接口
public interface Beverage {
    String getDescription();
    double cost();
}

// 具体饮料:浓缩咖啡
public class Espresso implements Beverage {
    @Override
    public String getDescription() {
        return "浓缩咖啡";
    }

    @Override
    public double cost() {
        return 25.0;
    }
}

// 具体饮料:美式咖啡
public class Americano implements Beverage {
    @Override
    public String getDescription() {
        return "美式咖啡";
    }

    @Override
    public double cost() {
        return 20.0;
    }
}

// 调料装饰器
public abstract class CondimentDecorator implements Beverage {
    protected Beverage beverage;

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }
}

// 牛奶装饰器
public class Milk extends CondimentDecorator {
    public Milk(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", 牛奶";
    }

    @Override
    public double cost() {
        return beverage.cost() + 5.0;
    }
}

// 摩卡装饰器
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() + 8.0;
    }
}

// 使用
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " ¥" + beverage.cost());

beverage = new Milk(beverage);
System.out.println(beverage.getDescription() + " ¥" + beverage.cost());

beverage = new Mocha(beverage);
System.out.println(beverage.getDescription() + " ¥" + beverage.cost());
// 输出:
// 浓缩咖啡 ¥25.0
// 浓缩咖啡, 牛奶 ¥30.0
// 浓缩咖啡, 牛奶, 摩卡 ¥38.0

Java IO中的装饰器

InputStream的装饰器?

答案:

java
// InputStream就是装饰器模式的典型应用
InputStream inputStream = new FileInputStream("file.txt");
inputStream = new BufferedInputStream(inputStream);  // 添加缓冲功能
inputStream = new DataInputStream(inputStream);  // 添加读取基本类型功能

// 装饰器链
InputStream in = new DataInputStream(
    new BufferedInputStream(
        new FileInputStream("file.txt")
    )
);

结构

InputStream (抽象组件)
├── FileInputStream (具体组件)
├── ByteArrayInputStream (具体组件)
└── FilterInputStream (装饰器)
    ├── BufferedInputStream (具体装饰器)
    ├── DataInputStream (具体装饰器)
    └── PushbackInputStream (具体装饰器)

Spring中的装饰器

BeanWrapper?

答案:

java
// BeanWrapper是对Bean的装饰
BeanWrapper bw = new BeanWrapperImpl(new User());
bw.setPropertyValue("name", "张三");
bw.setPropertyValue("age", 20);

练习题

  1. 装饰器模式和代理模式的区别?
  2. 装饰器模式和适配器模式的区别?
  3. 如何避免装饰器层次过深?
  4. Java IO为什么使用装饰器模式?
  5. 装饰器模式如何体现开闭原则?

Released under the MIT License.