装饰模式(Decorator Pattern)是一种结构模式(Structural Design Pattern)。装饰模式用于向一个类动态添加新的功能。在面向对象软件开发过程中,向类添加新功能至少有三种方法。第一种是直接在该类中添加新的功能。但是,这种实现方法可能会将该类变得格外复杂,难以维护。这也违背了单一功能原则(Single Responsibility Principle)。第二种方法是在该类的子类中实现新的功能。这是一种常见的实现方法,但是,这种方法可能存在两个缺陷。其一,新功能被"绑定"在类的继承关系之中。当需求发生变更时,代码较难维护。其二,当在一个类中添加新功能时,这个新功能可能会改变它的子类的行为。因此,装饰模式提出了第三种方式。与前两种方法不同的是,装饰模式能够将新功能动态的添加在一个类中,而前两种方法则是将代码静态的写入该类或者其子类之中。
装饰模式主要解决以下两种问题:
在装饰模式中,有四个参与方。
图一 装饰模式结构。
绘制一个平面窗口是一个经典的装饰模式的例子。我们也使用这个例子来展示装饰模式的使用方法。在一台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();
}
}
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());
}
}
}
本章介绍了装饰模式的结构和使用方法。使用装饰模式的优点是它分离了原有功能的实现和新增功能的实现。当需要时,开发人员能够动态的将新增功能添加到原有功能之上。与此同时,这种动态性也为开发人员提供了极大的灵活性。开发人员能够在运行时决定使用哪些新功能的组合,将其添加在原有功能之上。
注册用户登陆后可留言