天天看点

DECORATOR 装饰模式

DECORATOR  装饰模式   对象结构性模式

1、意图

动态地给一个对象增加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

2、别名

包装器Wrapper

3、动机

        有时我们希望给某对象而不是整个类添加一些功能。例如,一个图形用户界面工具箱允许你对任意一个用户界面组件添加一些特性,例如边框,或是一些行为,例如窗口滚动。

        使用继承机制是添加功能的一种有效途径,从其它类继承过来的边框特性可以被多个子类的实例所使用。但这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。

        一种较为灵活的方式是将组件嵌入另一个对象中,由这个对象添加边框。我们成这个嵌入的对象为装饰。这个装饰与它所装饰的组件接口一致,因此它对使用该组件的客户透明。它将客户请求转发给该组件,并且可能在转发前后执行一些额外的动作(例如画一个边框)。透明性使得你可以递归的嵌套多个装饰,从而可以添加任意多的功能,如下图所示。

DECORATOR 装饰模式

        例如,假定有一个对象TextView,它可以在窗口中显示正文。缺省的TextView没有滚动条,因为我们可能有时并不需要滚动条。当需要滚动条时,我们可以用ScrollDecorator添加滚动条。如果我们还想在TextView周围添加一个粗黑边框,我们可以使用BorderDecorator添加。因此只要简单地将这些装饰和TextView进行组合,就可以达到预期的效果。

        下面的对象图展示了如何将一个TextView对象与BorderDecorator以及ScrollDecorator对象组装起来产生一个具有边框和滚动条的文本显示窗口。

DECORATOR 装饰模式

        ScrollDecorator和BorderDecorator类是Decorator类的子类。Decorator类是一个可视组件的抽象类,用于装饰其它可视组件,如下图所示。

DECORATOR 装饰模式

        VisualComponent是一个描述可视对象的抽象类,它定义了绘制和事件处理的接口。注意Decorator类怎样将绘制请求简单地发送给它的组件,以及Decorator的子类如何扩展这个操作。

        Decorator的子类为特定功能可以自由地添加一些操作。例如,如果其他对象知道界面中恰好有一个ScrollDecorator对象,这些对象就可以用ScrollDecorator对象的ScrollTo操作滚动这个界面。这个模式中有一点很重要,它使得在VisualComponent可以出现的任何地方都可以有装饰。因此,客户通常不会感觉到装饰过的组件与未装饰组件之间的差异,也不会与装饰产生任何依赖关系。

4、适用性

        以下情况使用Decorator模式

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

        · 处理那些可以撤销的职责。

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

5、结构

DECORATOR 装饰模式

6、参与者

        · Component(VisualComponent)

          -- 定义一个对象接口,可以给这些对象动态地添加职责。

        · ConcreteComponent(TextView)

          -- 定义一个对象,可以给这个对象添加一些职责。

        · Decorator

          -- 维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。

        · ConcreteDecorator(BorderDecorator,ScrollDecorator)

          --向组件添加职责。

7、协作

        Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作。

8、效果

        Decorator模式至少有两个主要优点和两个缺点:

        1)比静态继承更灵活    与对象的静态继承(多重继承)相比,Decorator模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类(例如,BorderScrollableTextView,BorderedTextView)。这会产生许多新的类,并且会增加系统的复杂度。此外,为一个特定的Component类提供多个不同的Decorator类,这就使得你可以对一些职责进行混合和匹配。

          使用Decorator模式可以很容易地重复添加一个特性,例如在TextView上添加双边框时,仅需将添加两个BorderDecorator即可。而两次继承Border类则极容易出错。

        2)避免在层次结构高层的类有太多的特征    Decorator模式提供了一种“即用即付”的方法来添加职责。它并不师徒在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并且用Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。这样,应用程序不必为不需要的特征付出代价。同时也更易于不依赖于Decorator所扩展(甚至是不可预知的扩展)的类而独立地定义新类型的Decorator。扩展一个复杂类的时候,很可能会暴露与添加的职责无关的细节。

        3)Decorator与它的Component不一样    Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识。

        4)有许多小对象    采用Decorator模式进行系统设计往往会产生许多看上去很相似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是他们的类或是他们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对他们进行定制,但是很难学习这些系统,排错也很困难。

9、实现

        使用Decorator模式时应注意以下几点:

        1)接口的一致性    装饰对象的接口必须与它所装饰的Component的接口是一致的,因此,所有的ConcreteDecorator类必须有一个公共的父类(至少在C++中如此)。

        2)省略抽象的Decorator类    当你仅需要添加一个职责时,没有必要定义抽象Decorator类。你常常需要处理现存的类层次结构而不是设计一个新系统,这时你可以把Decorator向Component转发请求的职责合并到ConcreteDecorator中。

        3)保持Component类的简单性    为了保证接口的一致性,组件和装饰必须有一个公共的Component父类。因此保持这个类的简单性是很重要的;即,它应集中于定义接口而不是存储数据。对数据表示的定义应延迟到子类中,否则Component类会变得过于复杂和庞大,因而难以大量使用。赋予Component太多的功能也使得具体的子类有一些他们并不需要的功能的可能性大大增加。

        4)改变对象外壳与改变对象内核    我们可以将Decorator看做一个对象的外壳,它可以改变这个对象的行为。另外一种方法是改变对象的内核。例如,Strategy模式就是一个用于改变内核的很好的模式。

          当Component类原本就很庞大时,使用Decorator模式代价太高,Strategy模式相对更好一些。在Strategy模式中,组件将它的一些行为转发给一个独立的策略对象,我们可以替换strategy对象,从而改变或扩充组件的功能。

          例如我们可以将组件绘制边界的功能延迟到一个独立的Border对象中,这样就可以支持不同的边界风格。这个Border对象是一个Strategy对象,它封装了边界绘制策略。我们可以将策略的数目从一个扩充为任意多个,这样产生的效果与对装饰进行递归嵌套是一样的。

          由于Decorator模式仅从外部改变组件,因此组件无需对它的装饰有所了解;也就是说,这些装饰对该组件是透明的,如下图所示:

DECORATOR 装饰模式

          在Strategy模式中,component组件本身知道可能进行那些扩充,因此它必须引用并维护相应的策略,如下图所示:

DECORATOR 装饰模式

          基于Strategy的方法可能需要修改component组件以适应新的扩充。另一方面,一个策略可以有自己特定的借口,而装饰的接口则必须与组件的接口一致。例如,一个绘制边框的策略仅需要定义生成边框的接口(DrawBorder,GetWidth等),这意味着记事Component类很庞大时,策略也可以很小。

10、代码示例

        网络上代码示例很多,可随意参考。

继续阅读