深入浅出装饰模式

原创 2005年03月01日 22:52:00

一、引子

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

那设计模式中的装饰模式,是什么样子呢?

 

二、定义与结构

装饰模式(Decorator)也叫包装器模式(Wrapper)。GOF在《设计模式》一书中给出的定义为:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

       让我们来理解一下这句话。我们来设计这个类。假设你根据需求为类作了如下定义:

现在,在系统的一个地方需要一个能够报警的Door,你来怎么做呢?你或许写一个Door的子类AlarmDoor,在里面添加一个子类独有的方法alarm()。嗯,那在使用警报门的地方你必须让客户知道使用的是警报门,不然无法使用这个独有的方法。而且,这个还违反了Liskov替换原则。

也许你要说,那就把这个方法添加到Door里面,这样不就统一了?但是这样所有的门都必须有警报,至少是个哑巴警报。而当你的系统仅仅在一两个地方使用了警报门,这明显是不合理的——虽然可以使用缺省适配器来弥补一下。

       这时候,你可以考虑采用装饰模式来给门动态的添加些额外的功能。

       下面我们来看看装饰模式的组成,不要急着去解决上面的问题,到了下面自然就明白了!

1)        抽象构件角色(Component):定义一个抽象接口,以规范准备接收附加责任的对象。

2)        具体构件角色(Concrete Component):这是被装饰者,定义一个将要被装饰增加功能的类。

3)        装饰角色(Decorator):持有一个构件对象的实例,并定义了抽象构件定义的接口。

4)        具体装饰角色(Concrete Decorator):负责给构件添加增加的功能。

看下装饰模式的类图:

图中ConcreteComponent可能继承自其它的体系,而为了实现装饰模式,他还要实现Component接口。整个装饰模式的结构是按照组合模式来实现的,但是注意两者的目的是截然不同的(关于两者的不同请关注我以后的文章)。

 

三、举例

这个例子还是来自我最近在研究的JUnit,如果你对JUnit还不太了解,可以参考JUnit入门》JUnit源码分析(一)》JUnit源码分析(二)》JUnit源码分析(三)》。不愧是由GoF之一的Erich Gamma亲自开发的,小小的东西使用了N种的模式在里面。下面就来看看JUnit中的装饰模式。

       JUnit中,TestCase是一个很重要的类,允许对其进行功能扩展。

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

       呵呵,看看源代码吧,这个来的最直接!

       //这个就是抽象构件角色

       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);

}

 

//具体构件对象,但是这里是个抽象类

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);

       }

       ……

}

 

//装饰角色

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;

       }

}

 

       //具体装饰角色,这个类的增强作用就是可以设置测试类的执行次数

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);

让我们在回想下上面提到的的问题,这个警报门采用了装饰模式后,可以采用下面的方式来产生。

DoorDecorator alarmDoor = new AlarmDoor(new Door());

 

 

四、应用环境

       GOF书中给出了以下使用情况:

1)        在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

2)        处理那些可以撤消的职责。

3)        当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

来分析下JUnit的使用是属于哪种情况。首先实现了比静态继承更加灵活的方式,动态的增加功能。试想为Test的所有实现类通过继承来增加一个功能,意味着要添加不少的功能类似的子类,这明显是不太合适的。

而且,这就避免了高层的类具有太多的特征,比如上面提到的带有警报的抽象门类。

 

五、透明和半透明

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

Component c = new ConcreteComponent();

Component c1 = new ConcreteDecorator(c);

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

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

 

六、其它

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

 

七、总结

       终于写完了,不知道说出了本意没有。请指正!

 

深入浅出设计模式笔记之八:装饰模式

一、引子 装饰模式?肯定让你想起又黑又火的家庭装修来。其实两者在道理上还是有很多相像的地方。家庭装修无非就是要掩盖住原来实而不华的墙面,抹上一层华而不实的涂料,让生活多一点色彩。而墙还是那堵墙,他的...

深入浅出设计模式(九):12.装饰模式(Decorator) 13.桥模式(Bridge)14.策略模式(Strategy)

12.装饰模式(Decorator)在程序开发中,有时候开发人员会使用继承来扩展对象的功能,用户的需求是多变的,也就造成继承会造成代码的大范围改动,其实扩展对象的功能,采用组合比继承要好很多,当用户需...

装饰模式Demo

  • 2015年12月02日 01:17
  • 13KB
  • 下载

装饰模式 Decorator

1.装饰模式装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口, 装饰对象持有被装饰对象的实例.该模式中,要被扩展的类可以是包含抽象方法的抽象类, 也可以是包...

装饰模式生成密码实例

  • 2014年08月21日 10:03
  • 3KB
  • 下载

c# 一个简单的 装饰模式的例子

  • 2013年10月19日 21:58
  • 44KB
  • 下载

装饰模式(python)

装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。 应用场景:适用于 "新加入的功能仅仅是为了满足一些只在某些特定情况下才会执行的需求“。 优点: 1、把...

装饰模式简单例子

  • 2012年04月26日 10:47
  • 12KB
  • 下载

实验九:装饰模式.rar

  • 2011年05月26日 16:19
  • 25KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深入浅出装饰模式
举报原因:
原因补充:

(最多只允许输入30个字)