decorator

第十一章 装饰模式(Decorator Pattern)

1 概要

装饰模式(Decorator Pattern)是一种结构模式(Structural Design Pattern)。装饰模式用于向一个类动态添加新的功能。在面向对象软件开发过程中,向类添加新功能至少有三种方法。第一种是直接在该类中添加新的功能。但是,这种实现方法可能会将该类变得格外复杂,难以维护。这也违背了单一功能原则(Single Responsibility Principle)。第二种方法是在该类的子类中实现新的功能。这是一种常见的实现方法,但是,这种方法可能存在两个缺陷。其一,新功能被"绑定"在类的继承关系之中。当需求发生变更时,代码较难维护。其二,当在一个类中添加新功能时,这个新功能可能会改变它的子类的行为。因此,装饰模式提出了第三种方式。与前两种方法不同的是,装饰模式能够将新功能动态的添加在一个类中,而前两种方法则是将代码静态的写入该类或者其子类之中。

装饰模式主要解决以下两种问题:

  1. 在程序运行时,动态地向类或者对象添加新的功能。
  2. 如果应用场景不适合使用继承关系的话,装饰模式提供了另一种扩展功能的方法。

2 装饰模式的结构

在装饰模式中,有四个参与方。

  1. ComponentBase接口定义了某个功能的接口。
  2. ConcreteComponent类表示的是该功能具体的实现。
  3. Decorator类实现了ComponentBase接口,并且为装饰类提供了接口。
  4. ConcreteDecorator类实现某一个新功能的类。

图一 装饰模式结构

图一 装饰模式结构。

3 装饰模式示例

绘制一个平面窗口是一个经典的装饰模式的例子。我们也使用这个例子来展示装饰模式的使用方法。在一台Windows、Linux、或者MacBook桌面系统中,当用户运行一个应用程序时,系统会弹出一个窗口。在窗口中会显示这个应用程序的内容。我们定义了Window接口来表示窗口的接口,而使用SimpleWindow类来表示一个简单的窗口类。它实现了Window接口。

public interface Window {
    void draw();
}

class SimpleWindow implements Window {
    @Override
    public void draw() {
        System.out.println("Drawing a SimpleWindow.");
    }
}

抽象类WindowDecorator定义了装饰模式的接口。它实现了Window接口,并且有一个成员变量windowToBeDecorated,指向被装饰的窗口对象。

abstract class WindowDecorator implements Window {
    private final Window windowToBeDecorated;

    public WindowDecorator (Window windowToBeDecorated) {
        this.windowToBeDecorated = windowToBeDecorated;
    }

    @Override
    public void draw() {
        windowToBeDecorated.draw(); //Delegation
    }
}

当窗口内显示的内容较多时,窗口需要显示横向或者纵向的滚动条,允许用户横向或者纵向的滚动,查看其他内容。因此,我们使用了装饰模式,实现了VerticalScrollBarDecorator和HorizontalScrollBarDecorator类。它们分别表示带有横向和纵向滚动条的窗口。

从下面的代码中可以看出,VerticalScrollBarDecorator/HorizontalScrollBarDecorator与SimpleWindow的实现是相互独立的。当有一方发生变化时,不会影响另一方的实现。当VerticalScrollBarDecorator/HorizontalScrollBarDecorator的draw()方法调用时,首先会调用WindowDecorator::draw()方法,绘制整个窗口,然后再绘制横向/纵向滚动条,覆盖其相应的显示区域。

class HorizontalScrollBarDecorator extends WindowDecorator {
    public HorizontalScrollBarDecorator (Window windowToBeDecorated) {
        super(windowToBeDecorated);
    }

    @Override
    public void draw() {
        super.draw();
        drawHorizontalScrollBar();
    }

    private void drawHorizontalScrollBar() {
        System.out.println("Drawing a horizontal scroll bar.");
    }
}

class VerticalScrollBarDecorator extends WindowDecorator {
    public VerticalScrollBarDecorator (Window windowToBeDecorated) {
        super(windowToBeDecorated);
    }

    @Override
    public void draw() {
        super.draw();
        drawVerticalScrollBar();
    }

    private void drawVerticalScrollBar() {
        System.out.println("Drawing a vertical scroll bar.");
    }
}

在使用时,开发人员可以根据需求,动态地创建简单窗口或者带有滚动条的窗口。

public class DecoratorPatternExample {
    public static void main(String[] args) {
        // 创建一个简单窗口
        Window aSimpleWindow = new SimpleWindow();
        aSimpleWindow.draw();

        // 创建一个带有纵向滚动条的窗口
        Window aWindowWithVerticalBar = new VerticalScrollBarDecorator (new SimpleWindow());
        aWindowWithVerticalBar.draw();

        // 创建一个带有横向和纵向滚动条的窗口
        Window aWindowWithBothBars = new HorizontalScrollBarDecorator (
                new VerticalScrollBarDecorator (new SimpleWindow()));
        aWindowWithBothBars.draw();
    }
}

4 应用举例

Java标准库中也应用了装饰模式,例如:java.io.BufferedInputStream、java.io.FilterInputStream和相应的OutputStream类。BufferedInputStream为读取一个InputStream对象提供了缓存功能。

从BufferedInputStream的实现可以看出,BufferedInputStream继承自FilterInputStream。BufferedInputStream的构造函数接受一个InputStream对象,存放在FilterInputStream的成员变量in中。BufferedInputStream的成员变量数组buf,用于缓存读取的数据。

package java.io;

public class FilterInputStream extends InputStream {
    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
}

public class BufferedInputStream extends FilterInputStream {
    protected volatile byte[] buf;
    protected int count;
    protected int pos;

    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    public BufferedInputStream(InputStream in, int size) {
        super(in);
        ...
        buf = new byte[size];
    }

    public synchronized int read() throws IOException {
        ...
        return getBufIfOpen()[pos++] & 0xff;
    }
    ...
}

在使用时,BufferedInputStream在InputStream类的基础上增加了缓存功能。BufferedInputStream并没有继承FileInputStream类,而是继承自InputStream。因此,对照图一中的结构来看,本例中FileInputStream对应着图中左侧的ConcreteComponent;InputStream对应着图中ComponentBase;BufferedInputStream则扮演的是图中的Decorator。

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class BufferedInputStreamExample {
    public static void main(String args) {
        try {
            BufferedInputStream bufferedInputStream = 
                new BufferedInputStream(new FileInputStream("tempfile.txt"));
            
            bufferedInputStream.read();
        } catch (IOException ex) {
            System.err.println(ex.getMessage());
        }
    }
}

5 小结

本章介绍了装饰模式的结构和使用方法。使用装饰模式的优点是它分离了原有功能的实现和新增功能的实现。当需要时,开发人员能够动态的将新增功能添加到原有功能之上。与此同时,这种动态性也为开发人员提供了极大的灵活性。开发人员能够在运行时决定使用哪些新功能的组合,将其添加在原有功能之上。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.