目录

装饰模式

Decorator Pattern

装饰模式,是面向对象程式领域中,一种动态地往一个类别中添加新的行为的设计模式。就功能而言,装饰模式相比生成子类别更为灵活,这样可以给某个对象而不是整个类别添加一些功能。

OO原则:动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。装饰器模式符合设计原则中的里氏替换原则,具备很强的扩展性,最终满足开闭原则。

介绍

通过使用修饰模式,可以在运行时扩充一个类别的功能。原理是:增加一个修饰类包裹原来的类别,包裹的方式是在修饰类的构造函数中将原来的类以参数的形式传入。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类别中的方法。修饰类必须和原来的类别有相同的接口。

修饰模式是类别继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。

当有几个相互独立的功能需要扩充时,这个区别就变得很重要。在有些面向对象的编程语言中,类别不能在运行时被创建,通常在设计的时候也不能预测到有哪几种功能组合。这就意味着要为每一种组合创建一个新类别。相反,修饰模式是面向运行时候的对象实例的,这样就可以在运行时根据需要进行组合。一个修饰模式的示例是JAVA里的Java I/O Streams的实现。

/images/dp/Decorator_UML_class_diagram.svg
装饰模式 UML 类图

涉及角色

  • 抽象组件(Component):定义一个抽象接口,来规范准备附加功能的类。
  • 具体组件(ConcreteComponent):将要被附加功能的类,实现抽象构件角色接口。
  • 抽象装饰者(Decorator):持有对具体构件角色的引用并定义与抽象构件角色一致的接口。
  • 具体装饰者(ConcreteDecorator):实现抽象装饰者角色,负责为具体构件添加额外功能。

代码示例

抽象构件角色

1
2
3
4
5
// The Window interface class
public interface Window {
  public void draw(); // Draws the Window
  public String getDescription(); // Returns a description of the Window
}

具体构件角色

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// implementation of a simple Window without any scrollbars
public class SimpleWindow implements Window {
  public void draw() {
    // Draw window
  }

  public String getDescription() {
    return "simple window";
  }
}

抽象装饰者角色

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// abstract decorator class - note that it implements Window
public abstract class WindowDecorator implements Window {
    protected Window decoratedWindow; // the Window being decorated

    public WindowDecorator (Window decoratedWindow) {
        this.decoratedWindow = decoratedWindow;
    }
    
    @Override
    public void draw() {
        decoratedWindow.draw();
    }

    @Override
    public String getDescription() {
        return decoratedWindow.getDescription();
    }
}

具体装饰者角色

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// The first concrete decorator which adds vertical scrollbar functionality
public class VerticalScrollBar extends WindowDecorator {
  public VerticalScrollBar(Window windowToBeDecorated) {
    super(windowToBeDecorated);
  }

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

  private void drawVerticalScrollBar() {
    // Draw the vertical scrollbar
  }

  @Override
  public String getDescription() {
    return super.getDescription() + ", including vertical scrollbars";
  }
}


// The second concrete decorator which adds horizontal scrollbar functionality
public class HorizontalScrollBar extends WindowDecorator {
  public HorizontalScrollBar(Window windowToBeDecorated) {
    super(windowToBeDecorated);
  }

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

  private void drawHorizontalScrollBar() {
    // Draw the horizontal scrollbar
  }

  @Override
  public String getDescription() {
    return super.getDescription() + ", including horizontal scrollbars";
  }
}

测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class WindowDecoratorTest {

  // for print descriptions of the window subclasses
  void printInfo(Window w) {
    System.out.println("description:" + w.getDescription());
  }

  @Test
  void main() {
    // original SimpleWindow
    SimpleWindow sw = new SimpleWindow();
    printInfo(sw);
    // HorizontalScrollBar  mixed Window
    HorizontalScrollBar hbw = new HorizontalScrollBar(sw);
    printInfo(hbw);
    // VerticalScrollBar mixed Window
    VerticalScrollBar vbw = new VerticalScrollBar(hbw);
    printInfo(vbw);
  }
}

应用

Java IO 流为典型的装饰模式。

JDK

Java IO 划分为元素和行为两个部分,元素则是适配,行为则是装饰加强。

适配器模式主要在于字节流到字符流的转换和元素的包装上,如类:InputStreamReader, CharArrayReader, FileReader, PipedReader, StringReader。

装饰模式主要在对流的强化之中,如缓冲、过滤、行定位等,如类:BufferedReader, FilterReader, LineNumberReader。

1
BufferReader br = new BufferReader(new InputStreamReader(System.in));

综合了两种模式:把 InputStream 适配成 InputStreamReader,再把 InputStreamReader 加强装饰城 BufferedReader

MyBatis

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public interface Executor {
    int update(MappedStatement ms, Object parameter) throws SQLException;
}
// 二级缓存
public class CachingExecutor implements Executor {
  private final Executor delegate;
    public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
  }
}
// 一级缓存
public abstract class BaseExecutor implements Executor {
  protected Transaction transaction;
  protected Executor wrapper;
    protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.wrapper = this;
  }
}

BaseExecutor 有三个实现 SimpleExecutor,BatchExecutor,ReuseExecutor

装饰器 VS 静态代理

相同点

  • 都属于结构型设计模式。
  • 装饰者类与目标类要求实现同一接口;静态代理类与目标类要求也实现同一接口。
  • 装饰者类与静态代理类都可以实现增强目标类的功能。
  • 装饰者类与静态代理类中都具有目标类的引用,目的都是为了在其中调用目标类的方法。

不同点

  • 装饰者设计模式就是为了增强目标类;静态代理设计模式是为了保护和隐藏目标对象, 让客户类只能访问代理对象,而不能直接访问目标对象。
  • 装饰者类中的目标类的引用是通过带参构造器传入的;静态代理类中的目标类的引用, 一般都是在代理类中直接创建的,目的就是为了隐藏目标对象。
  • 装饰者基类一般不对目标对象进行增强,而是由不同的具体装饰者进行增强的,且这 些具体的装饰者可以形成增强链,对目标对象进行连续增强。静态代理类会直接对目标对象 进行增强,需要哪些增强的功能,一次性在静态代理类中完成,没有增强链的概念。

代理模式,侧重于不能直接访问一个对象,只能通过代理来间接访问,比如对象在另外一台机器上,或者对象被持久化了,对象是受保护的。对象在另外一台机器上,其实就是rpc,感兴趣的可以看看dubbo的源码本地访问的其实就是远程对象的代理,只不过代理帮你做了访问这个对象之前和之后的很多事情,但是对使用者是透明的了。

对象被持久化了,比如 MyBatis 的 MapperProxy。通过 xxxMapper 接口自动生成代理类。

装饰器模式是因为没法在编译器就确定一个对象的功能,需要运行时动态的给对象添加职责,所以只能把对象的功能拆成一一个个的小部分,动态组装。但是代理模式在编译器其实就已经确定了和代理对象的关系。