装饰器模式:动态地添加新功能,不改变对象原有行为

1,129 阅读11分钟

软件开发中,如何在不改变对象原有行为的情况下,动态地添加新的行为?如何让代码更加灵活和易于维护?

装饰器模式就是解决这些问题的良好实践。装饰器模式是一种常见的设计模式,它允许您在运行时动态地将新的行为添加到对象中,而无需更改其底层类的代码。这种模式可以帮助您更好地组织代码,使其更加灵活和可扩展。在本文中,我们将深入研究装饰器模式的定义、原理、实现方式以及它的应用场景,以便让您更好地理解和应用这种设计模式。

什么是装饰器模式

官方定义:

装饰器模式:动态地将责任添加到对象上。若要扩展功能,装饰器提供了比继承更有弹性的替代方案。

Decorator pattern: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

生活中的例子:

当你要买一杯咖啡时,你可以选择一个基本的咖啡类型,比如美式咖啡或拿铁咖啡,但你可能会想要添加一些额外的味道,比如巧克力或香草味道。你可以选择让服务员在咖啡中添加这些味道,这样就可以得到一个定制化的咖啡,而无需改变原始咖啡的基本类型。这就类似于使用装饰器模式,在不改变对象的基本功能的情况下,为对象添加新的功能。这种方式可以让你更灵活地定制咖啡的味道,而且可以在不改变原始咖啡类型的情况下实现这些扩展。

装饰器模式的组成部分

  1. 抽象组件(Component):它定义了对象的基本行为和属性,是装饰器和具体组件的基类。抽象组件通常是一个抽象类或接口。

  2. 具体组件(Concrete Component):它是抽象组件的子类,实现了抽象组件所定义的基本行为和属性。具体组件是装饰器模式的基础,可以被装饰器直接使用。

  3. 装饰器(Decorator):它是一个抽象类或接口,继承了抽象组件,并持有一个抽象组件对象的引用。装饰器的作用是动态地为组件添加新的行为和属性。装饰器可以被其他装饰器或具体组件所装饰,从而形成一条装饰链。

  4. 具体装饰器(Concrete Decorator):它是装饰器模式的具体实现,实现了装饰器所定义的行为和属性,并动态地为组件添加新的行为和属性。具体装饰器通常包含一个指向抽象组件对象的引用,可以通过构造函数或 setter 方法进行注入。

《设计模式》(Design Patterns)原文

  • Component: defines the interface for objects that can have responsibilities added to them dynamically.

  • ConcreteComponent: defines an object to which additional responsibilities can be attached.

  • Decorator: maintains a reference to a Component object and defines an interface that conforms to Component's interface.

  • ConcreteDecorator: adds responsibilities to the component.

装饰器模式的实现

那就拿上面那个例子来实现:

抽象组件(Component):

public interface Coffee {
    //获得描述
    String getDescription();
    //获取价格
    double cost();
}

具体组件(Concrete Component):

public class Espresso implements Coffee {
    public String getDescription() {
        return "Espresso";
    }
    public double cost() {
        return 1.99;
    }
}

装饰器(Decorator):

public abstract class CondimentDecorator implements Coffee {
    protected Coffee coffee;

    public CondimentDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    public abstract String getDescription();
    public abstract double cost();
}

具体装饰器(Concrete Decorator):

public class Chocolate extends CondimentDecorator {
    public Chocolate(Coffee coffee) {
        super(coffee);
    }

    public String getDescription() {
        return coffee.getDescription() + ", Chocolate";
    }

    public double cost() {
        return coffee.cost() + 0.2;
    }
}

Client

public class Main {
    public static void main(String[] args) {
        Coffee coffee = new Espresso(); // 初始咖啡为 Espresso
        coffee = new Chocolate(coffee); // 加上巧克力调料
        coffee = new Vanilla(coffee);   // 加上香草调料

        System.out.println(coffee.getDescription() + " $" + coffee.cost());
        // 输出:Espresso, Chocolate, Vanilla $2.29
    }
}

UML类图

image.png

装饰器模式在源码中的体现

- Java I/O库

抽象组件(Component): InputStream、OutputStream、Reader、和Writer作为基本抽象组件,它们是整个Java I/O类库的基础。这些类提供了基本的读写功能。

具体组件(Concrete Component):FileInputStream、FileOutputStream、FileReader、和FileWriter。这些类为具体的数据来源(如文件)提供了读写功能。

装饰器(Decorator):FilterInputStream、FilterOutputStream、FilterReader、和FilterWriter。这些类都继承了相应的基本抽象组件,并包含一个指向同类型基本抽象组件的引用。这样,它们可以将原始功能与额外功能(如缓冲、压缩等)结合起来。

具体装饰器(Concrete Decorator):BufferedInputStream、BufferedOutputStream、BufferedReader、和BufferedWriter。这些类继承了相应的抽象装饰器类,并实现了特定的额外功能。例如,Buffered系列类为读写操作添加了缓冲功能,从而提高了性能。

public class DecoratorExample {
    public static void main(String[] args) {
        String filePath = "test.txt";
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

- Spring Web的过滤器(Filter)

抽象组件(Component): javax.servlet.Filter接口,它定义了一个基本的过滤器,需要实现init()doFilter()destroy()方法。

具体组件(Concrete Component):具体组件是实现了Filter接口的类。例如,您可以创建一个日志记录过滤器或身份验证过滤器。这些具体组件可以独立使用,也可以与其他过滤器组合在一起。

装饰器(Decorator):在这种情况下,装饰器并没有显式地定义。Spring Web通过FilterChain(过滤器链)来实现过滤器的组合。FilterChain是一个职责链,它包含一组过滤器,这些过滤器按照注册顺序依次执行。FilterChain本身也可以看作是一个装饰器,因为它将一个过滤器的输出传递给另一个过滤器,从而实现了功能的组合。

具体装饰器(Concrete Decorator):在这种情况下,具体装饰器是实现了Filter接口的类。它们可以独立使用,也可以与FilterChain一起使用,以实现功能的组合。

这个spring的我举一个例子

public class LoggingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        System.out.println("Request: " + httpRequest.getRequestURI());

        chain.doFilter(request, response);

        System.out.println("Response: " + ((HttpServletResponse) response).getStatus());
    }

    @Override
    public void destroy() {
    }
}

在这个Spring Web过滤器的例子中,角色如下:

  • 抽象组件(Component):javax.servlet.Filter接口。这是整个过滤器体系的基础,所有过滤器都需要实现这个接口。Filter接口定义了过滤器的基本行为,包括init()doFilter()destroy()方法。

  • 具体组件(Concrete Component):在这个示例中,我们创建了具体组件:LoggingFilter(日志过滤器)。这具体组件实现了Filter接口,并完成了特定的功能。

  • 装饰器(Decorator):在这个示例中,装饰器的角色是由FilterChain隐式地扮演的。FilterChain负责按照注册顺序依次执行过滤器。FilterChain作为一个装饰器,将一个过滤器的输出传递给另一个过滤器,从而实现功能的组合。

  • 具体装饰器(Concrete Decorator):在这个示例中,LoggingFilter也充当具体装饰器的角色。它可以通过FilterChain与其他过滤器组合在一起,以实现功能的组合。

什么情况下使用装饰器模式

  1. 需要在不修改原有类的基础上,动态地扩展一个对象的功能。装饰器模式允许你为一个现有的对象添加新功能,而不需要更改原始类的代码。

  2. 需要为一个类的多个功能进行组合,而不是使用继承关系。装饰器模式可以避免类数量的指数级增长,特别是在需要组合多个独立的功能时。

让我们通过一个例子来具体说明这一点。假设你正在开发一个文本编辑器,该编辑器可以对文本应用不同的样式,如加粗、斜体、下划线等。如果使用继承来实现这些功能,你可能需要创建多个子类来表示所有可能的组合,如BoldTextItalicTextUnderlinedTextBoldItalicTextBoldUnderlinedTextItalicUnderlinedTextBoldItalicUnderlinedText。随着功能的增加,类的数量将指数级增长,导致系统变得复杂且难以维护。

使用装饰器模式,你可以为每个样式创建一个单独的装饰器类(如BoldDecoratorItalicDecoratorUnderlineDecorator),然后将这些装饰器动态地应用于文本对象。这种方式不仅减少了子类的数量,还提供了更灵活的功能组合方式。

例如,要创建一个既加粗又斜体的文本,你可以这样做:

Text text = new PlainText("Hello, world!");
Text boldText = new BoldDecorator(text);
Text boldItalicText = new ItalicDecorator(boldText);

同时,它还遵循了“组合优于继承”的软件设计原则。

  1. 需要遵循“开放/封闭原则”,即对扩展开放,对修改封闭。装饰器模式允许你在不修改现有代码的基础上扩展功能,使得系统更易于维护和升级。

  2. 当你需要能够在运行时动态地更改对象的行为。装饰器模式允许你在运行时为对象添加或删除功能,从而提供了更大的灵活性。

  3. 当子类的数量过多且不易维护时。装饰器模式可以减少子类的数量,从而降低系统的复杂性。

当一个基类有多个子类,每个子类都扩展了基类的一部分功能时,我们很容易遇到类数量膨胀的问题。这种情况下,类的继承结构变得复杂,难以理解和维护。而且,随着功能的增加,类的数量可能会指数级增长,导致系统变得更加复杂。

装饰器模式提供了一种更好的解决方案。通过将功能封装在装饰器中,我们可以动态地为对象添加或删除功能,而无需创建大量子类。这样,我们可以将功能的组合和扩展从继承关系中解耦,使代码结构更清晰、简洁和易于维护。

例如,在之前提到的文本编辑器示例中,如果使用继承来实现文本样式,我们需要创建许多子类来表示不同的样式组合。然而,通过使用装饰器模式,我们可以将每个样式作为一个独立的装饰器,然后动态地应用它们。这样,我们只需要创建一个基本的文本类和几个装饰器类,而不是大量的子类。这使得整个系统更易于理解和维护。

总之,当一个系统中有许多子类,每个子类都为基类添加一部分功能时,考虑使用装饰器模式。它可以减少子类的数量,降低系统的复杂性,并使代码更易于维护和扩展。

总结:

装饰器模式具有以下优点:

  1. 灵活性:装饰器模式允许在运行时动态地添加或删除对象的功能,而无需修改现有的代码。这提供了更大的灵活性,使得系统易于扩展和维护。

  2. 遵循开放/封闭原则:装饰器模式使得你可以在不修改现有类的基础上扩展功能。这样,你可以保持现有类的稳定性,同时还能方便地添加新功能。

  3. 减少子类数量:通过使用装饰器模式,你可以将功能封装在装饰器中,而不是创建大量子类。这降低了系统的复杂性,使代码更易于理解和维护。

  4. 组合优于继承:装饰器模式支持将多个功能组合在一起,而无需使用复杂的继承关系。这使得代码更加模块化,更容易进行单元测试和重用。

然而,装饰器模式也有一些缺点:

  1. 增加设计复杂性:虽然装饰器模式可以减少子类的数量,但它会引入额外的装饰器类和接口。这可能会使系统的设计变得更复杂,增加了学习和理解的难度。

  2. 实现复杂性:装饰器模式要求你正确地实现装饰器类和接口,以确保在组合和嵌套时能够正确地传递调用。这可能会导致实现起来比较困难和容易出错。

  3. 性能开销:装饰器模式可能会引入一些额外的性能开销,因为每个装饰器都需要将调用传递给其内部的组件。这种开销在大量使用装饰器时可能会变得显著。