基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

设计模式--装饰器模式 (Decorator)

知识点图片

装饰器模式是一种结构型设计模式,它允许在不修改对象代码的情况下,动态地为其添加新功能。它通过创建一个包装类来“包裹”原始对象,是比继承更灵活的替代方案。

我们来详细、清晰地讲解设计模式中的装饰器模式(Decorator Pattern)


1. 什么是装饰器模式?

一句话概括:

装饰器模式是一种结构型设计模式,它允许你在不修改现有对象代码的情况下,动态地为对象添加新的功能或职责。它通过创建一个包装(Wrapper)类来实现,这个包装类“包裹”了原始对象。

核心思想:

继承是一种扩展功能的方式,但它是静态的(在编译时确定)。装饰器模式提供了比继承更灵活的替代方案,可以在运行时动态地给对象添加功能。它遵循开闭原则(Open-Closed Principle):对扩展开放,对修改关闭。


2. 一个生动的比喻:喝咖啡

想象一下你去咖啡店点咖啡:

  1. 核心产品(Component):你先点一杯最基础的咖啡,比如 浓缩咖啡(Espresso)混合咖啡(HouseBlend)。这是你的核心对象。
  2. 加点料(Decorator):然后,你想要加一些调料,比如 牛奶(Milk)摩卡(Mocha)奶泡(Whip)。这些调料就是“装饰器”。
  3. 动态组合
    • 你可以只点一杯浓缩咖啡。
    • 你也可以点一杯浓缩咖啡,然后加上牛奶
    • 你还可以再在加了牛奶的咖啡上再加上奶泡
    • 甚至可以点一杯加了双份摩卡一份奶泡的混合咖啡。

在这个过程中:

  • 咖啡本身没有被改变。
  • 调料(装饰器)被一层一层地“包裹”在咖啡外面。
  • 每一层装饰器都增加了新的“风味”(功能)和“价格”(职责)。
  • 最终你得到的是一个组合了多种功能的对象,但从外部看,它仍然是一杯“咖啡”(拥有共同的接口)。

3. 装饰器模式的结构

装饰器模式通常包含以下四个核心角色:

  1. Component(组件接口)

    • 定义了原始对象和装饰器对象的共同接口。客户端代码可以统一地处理这两种对象。
    • 咖啡比喻中:Beverage(饮料)接口,定义了 getDescription()cost() 方法。
  2. ConcreteComponent(具体组件)

    • 这是被装饰的原始对象,它实现了 Component 接口。
    • 咖啡比喻中:Espresso(浓缩咖啡)类,实现了 Beverage 接口。
  3. Decorator(抽象装饰器)

    • 这是一个抽象类,它也实现了 Component 接口。
    • 关键点:它内部持有一个 Component 对象的引用(通过组合)。
    • 它的作用是作为所有具体装饰器的基类,将所有请求转发给它所包裹的 Component 对象。
    • 咖啡比喻中:CondimentDecorator(调料装饰器)抽象类。
  4. ConcreteDecorator(具体装饰器)

    • 这是实际的装饰器类,负责为 Component 对象添加新的职责。
    • 它会重写父类(Decorator)的方法,在调用被包裹对象(wrapped object)的同样方法之前或之后,加上自己的逻辑。
    • 咖啡比喻中:Milk(牛奶)、Mocha(摩卡)等具体类。

4. Java 代码示例(沿用咖啡比喻)

第1步:定义组件接口 (Component)

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

第2步:创建具体组件 (ConcreteComponent)

java
// ConcreteComponent: 浓缩咖啡
public class Espresso implements Beverage {
    @Override
    public String getDescription() {
        return "Espresso";
    }

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

// ConcreteComponent: 混合咖啡
public class HouseBlend implements Beverage {
    @Override
    public String getDescription() {
        return "House Blend Coffee";
    }

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

第3步:创建抽象装饰器 (Decorator)

java
// Decorator: 调料装饰器(抽象类)
public abstract class CondimentDecorator implements Beverage {
    // 持有被装饰者的引用
    protected Beverage beverage;

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

    // 装饰器需要重新实现getDescription,具体由子类完成
    @Override
    public abstract String getDescription();
}

注意:getDescription() 在这里被定义为抽象方法,是为了强制子类提供自己的描述。cost() 方法则没有,因为大多数调料的成本计算方式是 自己的价格 + 被包裹饮料的价格,可以在子类中直接实现。

第4步:创建具体装饰器 (ConcreteDecorator)

java
// ConcreteDecorator: 摩卡
public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        // 在原有描述上加上自己的描述
        return beverage.getDescription() + ", Mocha";
    }

    @Override
    public double cost() {
        // 在原有价格上加上自己的价格
        return beverage.cost() + 0.20;
    }
}

// ConcreteDecorator: 牛奶
public class Milk extends CondimentDecorator {
    public Milk(Beverage beverage) {
        super(beverage);
    }

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

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

// ConcreteDecorator: 奶泡
public class Whip extends CondimentDecorator {
    public Whip(Beverage beverage) {
        super(beverage);
    }

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

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

第5步:客户端测试

java
public class CoffeeShop {
    public static void main(String[] args) {
        // 1. 点一杯最简单的浓缩咖啡
        Beverage beverage1 = new Espresso();
        System.out.println("Description: " + beverage1.getDescription());
        System.out.println("Cost: $" + beverage1.cost());
        // 输出:
        // Description: Espresso
        // Cost: $1.99

        System.out.println("--------------------");

        // 2. 点一杯混合咖啡,加双份摩卡和一份奶泡
        Beverage beverage2 = new HouseBlend(); // 先来一杯基础咖啡
        beverage2 = new Mocha(beverage2);      // 用Mocha装饰它
        beverage2 = new Mocha(beverage2);      // 再用Mocha装饰一次
        beverage2 = new Whip(beverage2);       // 最后用Whip装饰

        System.out.println("Description: " + beverage2.getDescription());
        System.out.println("Cost: $" + beverage2.cost());
        // 输出:
        // Description: House Blend Coffee, Mocha, Mocha, Whip
        // Cost: $1.44  (0.89 + 0.20 + 0.20 + 0.15)
    }
}

5. 优点和缺点

优点

  1. 高度灵活性:可以在运行时动态地添加或删除功能,比静态继承灵活得多。
  2. 遵循开闭原则:可以轻松引入新的装饰器,而无需修改现有的组件或装饰器代码。
  3. 避免类爆炸:如果使用继承,每一种功能的组合都需要一个子类(如 EspressoWithMilkEspressoWithMochaEspressoWithMilkAndMocha...),会导致类的数量急剧增加。装饰器模式用少量的类就能实现这些组合。
  4. 职责单一:每个装饰器类只关心自己的那部分功能,使代码更易于维护。

缺点

  1. 增加复杂性:会引入许多小的、功能单一的对象,使得代码的结构变得更加复杂,调试时需要穿过多个包装层。
  2. 对象身份问题:从外部看,装饰后的对象和原始对象是不同的。例如,beverage2 instanceof HouseBlend 会返回 false,因为 beverage2 的最外层类型是 Whip

6. 适用场景

  1. 当你希望在不影响其他对象的情况下,以动态、透明的方式向单个对象添加功能时。
  2. 当使用子类继承来扩展功能会导致类的数量失控时。
  3. 当你无法使用继承来扩展功能时(例如,类被 final 修饰)。
  4. 当一个对象的职责可以被分离,并且可以动态地组合这些职责时。

真实世界的例子

  • Java I/O 类库:这是装饰器模式最经典的例子。
    • FileInputStream 是一个具体组件(ConcreteComponent),用于从文件读取字节。
    • BufferedInputStream 是一个具体装饰器(ConcreteDecorator),它包裹了 InputStreamFileInputStream 的父类),并为其添加了缓冲功能,提高了读取性能。
    • ObjectInputStream 也是一个装饰器,它包裹了其他 InputStream,并添加了反序列化对象的功能。
    • 你可以这样组合它们:new ObjectInputStream(new BufferedInputStream(new FileInputStream("data.txt")))
00:00
00:00