26 设计模式:装饰器模式--结构型模式

94 阅读6分钟

结构型模式之:

  • 适配器模式
  • 桥接模式
  • 代理模式
  • 装饰模式
  • 组合模式
  • 外观模式
  • 享元模式

1.什么是装饰器模式?

装饰器模式是一种结构型设计模式,它允许在不改变对象自身结构的情况下动态地添加功能或责任。装饰器模式通过创建一个包装对象来实现,该包装对象包含原始对象,并提供了与原始对象相同的接口,从而使客户端无需改变代码即可使用新功能。

假设有一个接口 Shape,定义了绘制形状的操作:

public interface Shape {
    void draw();
}

然后有一个具体的形状类 Rectangle,用于绘制矩形:

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Rectangle");
    }
}

现在,我们希望在绘制矩形时添加一些额外的功能,例如添加边框或阴影,而又不希望修改原始的 Rectangle 类。这时就可以使用装饰器模式来实现。

首先,我们创建一个装饰器抽象类 ShapeDecorator,实现 Shape 接口:

public abstract class ShapeDecorator implements Shape {
    protected Shape decoratedShape;

    public ShapeDecorator(Shape decoratedShape) {
        this.decoratedShape = decoratedShape;
    }

    @Override
    public void draw() {
        decoratedShape.draw();
    }
}

然后,我们创建具体的装饰器类,例如 BorderDecorator,用于添加边框:

public class BorderDecorator extends ShapeDecorator {
    public BorderDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }

    @Override
    public void draw() {
        decoratedShape.draw();
        addBorder();
    }

    private void addBorder() {
        System.out.println("Adding Border");
    }
}

客户端可以直接使用装饰器对象来装饰原始对象,例如:

public class Client {
    public static void main(String[] args) {
        // 创建一个原始的矩形对象
        Shape rectangle = new Rectangle();

        // 使用装饰器给矩形添加边框
        Shape decoratedRectangle = new BorderDecorator(rectangle);

        // 绘制装饰后的矩形
        decoratedRectangle.draw();
    }
}

通过装饰器模式,我们可以在运行时动态地给对象添加新功能,而无需改变其结构。这种灵活性使得装饰器模式在需要对对象进行逐步功能增强或者灵活组合时非常有用。

2.装饰器模式的实现

在现代代码框架中,装饰器模式经常被用于增强类库或框架中的一些核心组件,以便于在不改变原有代码的情况下,动态地为对象添加新的功能或行为。一个常见的例子是 Java 中的 I/O 类库中的装饰器模式。

在 Java 的 I/O 类库中,装饰器模式被广泛应用,例如 InputStreamOutputStream。这些类都是抽象基类,用于处理输入和输出流。Java 提供了许多装饰器类,用于在输入和输出流上添加额外的功能,如缓冲、加密、压缩等。

import java.io.*;

public class Main {
    public static void main(String[] args) {
        try {
            // 创建文件输入流
            InputStream inputStream = new FileInputStream("input.txt");

            // 使用装饰器添加缓冲功能
            InputStream bufferedInputStream = new BufferedInputStream(inputStream);

            // 使用装饰器添加解压缩功能
            InputStream zipInputStream = new GZIPInputStream(bufferedInputStream);

            // 读取数据
            int data;
            while ((data = zipInputStream.read()) != -1) {
                System.out.print((char) data);
            }

            // 关闭流
            zipInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个例子中,FileInputStream 是一个原始的输入流对象,用于从文件中读取数据。然后,通过装饰器模式,我们使用 BufferedInputStream 装饰了 FileInputStream,添加了缓冲功能;接着,又使用 GZIPInputStream 装饰了 BufferedInputStream,添加了解压缩功能。

通过上面的例子,做个 形象的比喻,装饰器模式是在对象处理过程中,在保持对象不变的情况下,增强对象的功能的设计模式。 类似于洗车时,第一个模块洗脚踏垫,然后下个工人洗车轮,最后一个工人再洗车身。最后洗完之后,还是一辆车,为了保持对象的统一,我们每个流程的工人接受的都是一辆汽车这个对象,因为你不能保证其他流程的工人有没有使用机关枪把车都洗坏(也就是都继承同一个接口或者类,目前推荐多用组合额少用继承,使用组合代替继承,所以就通过动态组合的方式,来传入相同的对象

image.png

3.装饰器模式的使用场景

  1. 动态地为对象添加额外的功能: 装饰器模式允许你在运行时动态地为对象添加新的功能,而无需修改其现有的代码结构。这对于需要在不同的情况下添加不同的功能时非常有用。
  2. 避免子类爆炸性增长: 装饰器模式可以避免通过创建大量的子类来实现不同功能组合的问题。通过将功能封装在装饰器中,可以避免类的爆炸性增长。
  3. 透明地扩展对象功能: 使用装饰器模式,可以在不影响现有代码的情况下透明地扩展对象的功能。客户端代码可以使用装饰器对象来操作目标对象,而不需要知道装饰器的存在。
  4. 遵循开闭原则: 装饰器模式有助于遵循开闭原则,即对扩展开放,对修改关闭。通过添加新的装饰器类,可以在不修改现有代码的情况下扩展对象的功能。
  5. 组合多个功能: 装饰器模式允许你将多个装饰器按照特定的顺序组合起来,从而形成一个复杂的功能组合。这种组合方式非常灵活,可以根据需求随意组合不同的功能。
  6. 如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。  许多编程语言使用 final最终关键字来限制对某个类的进一步扩展。 复用最终类已有行为的唯一方法是使用装饰模式: 用封装器对其进行封装。

4.装饰器模式的优缺点

优点

  • 你无需创建新子类即可扩展对象的行为。
  • 你可以在运行时添加或删除对象的功能。
  • 你可以用多个装饰封装对象来组合几种行为。
  • 单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类

缺点

  • 多层嵌套复杂性: 使用装饰器模式时,可能会产生多层嵌套的装饰器对象,这会增加代码的复杂性,降低代码的可读性和可维护性。
  • 可能引入大量的小对象: 每个装饰器类都是一个单独的对象,如果系统中存在大量的装饰器类,可能会引入大量的小对象,增加内存和性能消耗。
  • 易于滥用: 装饰器模式是一种灵活的设计模式,但也容易被滥用。如果过度使用装饰器模式,可能会导致类的层次结构变得复杂,使系统变得难以理解和维护。
  • 不适合所有情况: 装饰器模式适用于需要动态地添加或修改对象功能的场景,但并不是所有情况都适合使用装饰器模式。在某些情况下,使用其他设计模式可能更合适。
  • 性能影响: 在运行时动态地添加功能可能会对系统的性能产生一定的影响。由于装饰器模式涉及对象的多次包装和解包操作,可能会导致额外的性能开销。