引子

  • 装饰模式?肯定让你想起又黑又火的家庭装修来。其实两者在道理上还是有很多相像的地方。
  • 家庭装修无非就是要掩盖住原来实而不华的墙面,抹上一层华而不实的涂料,让生活多一点色彩。而墙还是那堵墙,他的本质一点都没有变,只是多了一层外衣而已。
  • 那设计模式中的装饰模式,是什么样子呢?

定义与结构

  • 装饰模式(Decorator)也叫包装器模式(Wrapper)。

  • GOF 在《设计模式》一书中给出 的定义为:动态地给一个对象添加一些额外的职责。

  • 就增加功能来说,Decorator 模式相比生成子类更为灵活。

举例

  • 下面就来看看 JUnit 中的装饰模式。 在 JUnit 中,TestCase 是一个很重要的类,允许对其进行功能扩展。

  • 在 junit.extensions 包中,TestDecorator、RepeatedTest 便是对 TestCase 的装饰模式 扩展。下面我们将它们和上面的角色对号入座。

代码

  • 抽象构件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface Test {
    /**
    * Counts the number of test cases that will be run by this test.
    */
    public abstract int countTestCases();
    /**
    * Runs a test and collects its result in a TestResult instance.
    */
    public abstract void run(TestResult result);
    }
  • 具体构件对象,但是这里是个抽象类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public abstract class TestCase extends Assert implements Test {
    public int countTestCases() {
    return 1;
    }
    public TestResult run() {
    TestResult result= createResult();
    run(result);
    return result;
    }
    public void run(TestResult result) {
    result.run(this);
    }
    }
  • 装饰角色

    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
    public class TestDecorator extends Assert implements Test {
    //这里按照上面的要求,保留了一个对构件对象的实例
    protected Test fTest;
    public TestDecorator(Test test) {
    fTest= test;
    }
    /**
    * The basic run behaviour.
    */
    public void basicRun(TestResult result) {
    fTest.run(result);
    }
    public int countTestCases() {
    return fTest.countTestCases();
    }
    public void run(TestResult result) {
    basicRun(result);
    }
    public String toString() {
    return fTest.toString();
    }
    public Test getTest() {
    return fTest;
    }
    }
  • 具体装饰角色,这个类的增强作用就是可以设置测试类的执行次数

    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
    public class RepeatedTest extends TestDecorator {
    private int fTimesRepeat;
    public RepeatedTest(Test test, int repeat) {
    super(test);
    if (repeat < 0)
    throw new IllegalArgumentException("Repetition count must be > 0");
    fTimesRepeat= repeat;
    }

    //看看怎么装饰的吧
    public int countTestCases() {
    return super.countTestCases()*fTimesRepeat;
    }

    public void run(TestResult result) {
    for (int i= 0; i < fTimesRepeat; i++) {
    if (result.shouldStop())
    break;
    super.run(result);
    }
    }

    public String toString() {
    return super.toString()+"(repeated)";
    }
    }
  • 使用的时候,就可以采用下面的方式: TestDecorator test = new RepeatedTest(new TestXXX() , 3);

透明和半透明

  • 对于面向接口编程,应该尽量使客户程序不知道具体的类型,而应该对一个接口操作。 这样就要求装饰角色和具体装饰角色要满足 Liskov 替换原则。像下面这样:

    1
    2
    Component c = new ConcreteComponent();
    Component c1 = new ConcreteDecorator(c);
  • JUnit 中就属于这种应用,这种方式被称为透明式。

  • 而在实际应用中,比如 java.io 中往 往因为要对原有接口做太多的扩展而需要公开新的方法(这也是为了重用)。所以往往不能 对客户程序隐瞒具体的类型。这种方式称为“半透明式”。

  • 在 java.io 中,并不是纯装饰模式的范例,它是装饰模式、适配器模式的混合使用

其它

  • 采用 Decorator 模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅 在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。
  • 尽管对于 那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困 难。这是 GOF 提到的装饰模式的缺点,你能体会吗?
  • 他们所说的小对象我认为是指的具体 装饰角色。这是为一个对象动态添加功能所带来的副作用。