`
isiqi
  • 浏览: 15946712 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

用三种方式装饰(Decorate)代码

阅读更多

摘要


本文描述了Decorator模式的三种不同实现:继承、封装与外挂。深入讨论了每一种实现方式的优点与不足。

原文链接:Threeapproachesfordecoratingyourcode

每一们读过GoF的那本著名的设计模式一书的人都会知道Decorator模式。现在,让我们暂时
忘记所了解的Decorator概念,尝试着从我们的开发经验中去理解Decorator模式吧。

Decorator是用于装饰一个事物(或人)的另一个事物(或人)。一个Decorator直接改变被
装饰对象的职责或特征,但是不能改变被装饰对象的自有属性。例如:一个镜框可以装饰图
片,化妆品可以装饰女孩的脸等等。

从我们的专业角度来讨论一些存在的实例:

1JScrollPane可以装饰JComponent的视图部分。JComponent本身并不会被改变,但是增加
了一个新的属性(可滚动)。

2BufferedInputStream是InputStream的装饰子,本身BufferedInputStream就是一个
InputStream,但是它更快,因为提供了对数据的缓存。

3考虑一下DebugButton,它与JButton一样,但是它在被点击时可以向日志文件添加消息。
DebugButton是JButton的装饰子,因为它直接改变了JButton但并没有改变它的自有属性。


4再又如ScrollOverButton,它增加了一个鼠标滑过的行为。当鼠标移出时它是平的,当鼠
标经过时它具有一个凸起的边框。很显然,ScrollOverButton也是JButton的装饰子。

现在,我们知道Decorator可能有三种不同的实现:

1继承(Inheritance)
2封装(Wrapper)
3外挂(External)

本文将讨论每一个实现模型,以及它们的优缺点。

继承


对于开发人员而言,最直观的Decorator实现就是:写一个派生类,它继承自被装饰类,并
赋于新的职责。新的职责可以是通过增加方法或是修改已有方法来实现。

publicclassDebugButtonextendsJButton
{
publicDebugButton()
{
addActionListener(newActionListener()
{
System.out.println("debugmessage");
});
}
}


此外,我们也可以用相同的方式来实现ScrollOverButton:不是增加ActionListener,而是
增加MouseListener。在MouseListener回调方法中改变JButton的边框,当mouseEntered()
被调用时,将边框从EmpetyBorder变为RaisedBevelBorder。而当mouseExited()方法被调用
时,再将边框从RaisedBevelBorder恢复成EmpetyBorder。

对于BufferedInputStream,同样实现也是非常简单的。修改每个读数据的方法,让它从内
存缓冲区来读取数据。如果缓冲区是空的,它可以通过super.read()方法来获取数据并填充
缓冲区。JScrollPane,要实现起来就有点复杂,下面我将讨论为什么它会比较难以用继承的方式来实现。

讨论一下继承方式实现Decorator模式的优点与缺点:

优点
1我们几乎可以用这个方式实现所有的Decorator。
2使用继承方式实现Decorator模式,可以保留被装饰类的原始类型,这一点是非常重要
的。用继承方式,我们仍可以使用被装饰类的在被装饰之前的类型,例如,我们可以在我们
的应用程序中使用ScrollOverButton代替JButton,但是JScrollPane就不能代替包含在它内
部的对象。

缺点
1用继承的方式仍不够直接。设想一下我们实现了ScrollOverButton和DebugButton,但是
我们又需要实现一个既有ScrollOverButton特点又有DebugButton行为的按钮。怎么办?用
继承方式我们唯一的选择就是再派生出一个ScrollOverDebugButton类。如果我们有了
ScrollOverDebugButton的实现,那么是否还需要继续保留ScrollOverButton或DebugButton
实现?因为我们可以为ScrollOverDebugButton增加两对方法来打开或关闭debug或scroll-
over的行为:

publicvoidsetDebug(booleanb);
publicbooleanisDebug();

publicvoidsetScrollOver(booleanb);
publicbooleanisScrollOver();


再进一步考虑,如果将来我们有更多的装饰功能,增加新的U1,U2,......Un个行为。我们是
不是要写一个类,叫U1U2...UnButton?它是不是要包括2n个这样的方法:

publicvoidsetU(booleanb);
publicbooleangetU;();


每增加一个新的行为(Un+1)给装饰器就需要增加两个新的方法,并要修改这个装饰器的代
码实现。这明显与面向对象的思想相悖,可能会产生严重的后果。(注意:
javax.swing.JButton就是这样实现的)。

2多数可视化对象的行为是由风格参数来指定的,而风格的改变是不可预知的。当风格发生
了改变,我们不得不调整自己的改变。正如上面所述,使用继承的方式可能需要改变实现的
代码。

3要保证被装饰类的原始类型也不是一件容易的事。我们需要重载每个构造子,有时甚至是
静态方式。尽管这不困难,但总是相当麻烦的一件事。

用继承方式来实现Decorator模式并不象我们先前想像的那么简单。许多时候,我们并不知
道将来我们需要哪一些装饰器,结果是,使用继承方式的Decorator在扩展性方面相当困
难,并且与面向对象的原则会产生冲突。

封装(Wrapper)


封装实现最主要的思想是将被装饰的对象封装入Decorator模式中。Decorator将外界请求转
发给封装的被装饰对象,并且在转发之前(或之后)执行自己新增的功能,或者也可以提供
新的独立方法来实现新增功能。

让我们回到刚才的例子并且重新把它们用封装方式来实现:

1BufferedInputStream是一个InputStream的封装,(关于这一点可以参考JDK中
java.io.BufferedInputStream类的说明或源码)。尽管事实上BufferedInputStream也是
InputStream的一个派生类。作为封装,在BufferedInputStream的构造子中获取了另一个
InputStream对象,并且将它作为实例变量保存起来,然后它可以转发请求到这个内置的
InputStream对象中。我们可以使用BufferedInputStream在我们原来使用InputStream场合。

2JScrollPane也是一个封装的实现。它转发请求到被封装的组件中(它们被称之为视
图)。要注意的是,我们不能够使用JScrollPane代替它内部的组件,因为它不支持所有的
视图功能。例如,在JScrollPane的getFont()返回的是JScrollPane的字体而不是视图的字体。

3我们可以用这种方式实现DebugButton:


publicclassDebugButtonextendsJButtonimplementsActionListener
{
privateJButtonbutt=null;
publicDebugButton(JButtonbutt)
{
this.butt=butt;
butt.addActionListener(this);
}

//ActionListener
publicvoidactionPerformed(ActionEvente)
{
System.out.println("debugmessageforbutton"+butt);
}

........
/*需要提供约180个类似这样的方法:
anyJButtonmethodM(params)
{
butt.M(params)
}
*/


这保持了被装饰对象的类型(它继承自JButton),但是这仍看上去不是那么直接。
注意:我们不能够使用java.lang.reflect.Proxy来作为代理,因为JButton是一个类而不是一个接口。

另一种实现可以这样:

publicclassDebugButtonextendsJComponentimplementsActionListener
{
privateJButtonbutt=null;
publicDebugButton(JButtonbutt)
{
this.butt=butt;
butt.addActionListener(this);
}
publicJButtongetUnderlineButton()
{
returnbutt;
}

//ActionListener
publicvoidactionPerformed(ActionEvente)
{
System.out.println("debugmessageforbutton"+butt);
}

........

/*
可以实现一些(不多)可选的方法,象get/setFont,get/setBackground等。
*/

}

这个实现方式相当简单,但这样的DebugButton不是从JButton派生出来的,我们不可以用
DebugButton代替JButton。JScrollPane就是用这种方式实现的。

4在ScrollOverButton也存在与DebugButton同样的问题。从JButton派生则可能导致额外的
代码,但可以保持JButton类型,如果从JComponent派生则可以更简单和直接,但它不能保
持JButton类型。

也来讨论一下封装方式的优点与缺点:

优点
正如上文所述,用封装实现Decorator可以减少所需要提供的方法,降低编码量(象
InputStream)。所有的优点都可以归结为这种实现方式可以得到短小精悍的类。

1实现足够简单,并可以保持被封装对象的类型
2每个装饰器独立于其它装饰器。
3在许多场合,可以同时使用多个装饰器。


缺点
然而,对于那些本身有众多方法的类,使用封装也会导致非常冗长的类代码。对于可视化
的对象,我们需要提供上百个方法或是牺牲装饰对象的类型。

根据GoF书中所言,封装(Wrapper)才是真正意义上的装饰器。它适用于代码短小的被装饰
类。对于长的类,开发人员不得不作出抉择:是提供上百个方法以保持被装饰对象的原有类
型?还是牺牲被装饰对象的类型来换取简单精炼的代码?

外挂



为了描述这种外挂的实现方式,让我们来看一下DebugButton和DebugDecorator类的实现代码:


publicclassDebugDecoratorimplementsActionListener
{
publicvoiddecorateDebug(JButtonbutt)
{
butt.addActonListenr(this);
}
publicvoidundecorateDebug(JButtonbutt)
{
butt.removeActonListenr(this);
}

//ActionListener
publicvoidactionPerformed(ActionEventevt)
{
JButtonsrc=(JButton)evt.getSource();
System.out.println("debugmessageforbutton"+src);
}
}


方法decorateDebug()增加了一个ActionListener,方法undecorateDebug()则移除
ActionListener。方法actionPerformed()负责输出debug信息。

现在,看看如何使用上面的DebugDecorator类:

DebugDecoratordecor=newDebugDecorator();
........
JButtonmyButt=...


........

//Addexternaldecorator
decor.decorateDebug(myButt);

.........

//Removeexternaldecorator
decor.undecorateDebug(myButt);

.........

同样的方式,我们可以实现RollOverDecorator类。在代码中同时使用两个装饰器可以这样:

DebugDecoratordebugDecor=newDebugDecorator();
DebugDecoratorrollDecor=newDebugDecorator();
........
JButtonmyButt=...


........

//Adddebugdecorator
debugDecor.decorateDebug(myButt);
........

//AddrollOverdecorator
rollDecor.decorateRollOver(myButt);

.........

........

//Removedebugdecorator
debugDecor.undecorateDebug(myButt);
........

//RemoverollOverdecorator
rollDecor.undecorateRollOver(myButt);


注意:在增加一个新的装饰器就可以得到新的行为而不需要更改任何代码。

我们可以应用一个DebugDecorator给任意多个JButton。从这点来说,在一个JVM中只需要一个DebugDecorator实例即足够了,所以DebugDecorator可以实现为单体模式。

我把这种单体称之为“单体装饰器”,它可以(不是必须)有多于一个实例。而原则上的单体只能有一个实例。

现在看看重构的DebugDecorator:


publicclassDebugDecoratorimplementsActionListener
{
privatestaticfinalDebugDecoratorinst=newDebugDecorator();

publicstaticgetInstance()
{
returninst;
}
publicvoiddecorateDebug(JButtonbutt)
{
butt.addActonListenr(this);
}
publicvoidundecorateDebug(JButtonbutt)
{
butt.removeActonListenr(this);
}

//ActionListener
publicvoidactionPerformed(ActionEventevt)
{
JButtonsrc=(JButton)evt.getSource();
System.out.println("debugmessageforbutton"+src);
}
}

它的用法如下:

JButtonmyButt=...
........

//Addexternaldecorator
DebugDecorator.getInstance().decorateDebug(myButt);

.........

//Removeexternaldecorator
DebugDecorator.getInstance().undecorateDebug(myButt);

.........

再增加新的decorate()方法和undecoratedDebugContainer()方法:

publicvoiddecorateDebugContainer(JComponentcontainer)
{
Component[]comps=container.getComponents();
for(inti=0;i<comps.length;i++)
{
if(JButton.class.isInstance(comps[i]))
{
comps[i].addActionListener(this);
}
}
}

publicvoidundecorateDebugContainer(JComponentcontainer)
{
Component[]comps=container.getComponents();
for(inti=0;i<comps.length;i++)
{
if(JButton.class.isInstance(comps[i]))
{
comps[i].removeActionListener(this);
}
}
}

这样,我们就可以方便地将DebugDecorator应用于容器(例如ToolBar)了。

同样我们也可以编写出RollOverDecorator的实现,但是不能实现BufferedInputStream因为没有合适的监听器和回调方法。

再讨论一下外挂方式的优点与缺点:

优点

这种实现永远不需要改变被装饰对象的类型。
只要我们需要,就可以编写任意多的装饰器,并且可以任意地增加或移去它们。
每个装饰器都是独立于其它装饰器。
在增加或移去装饰器后,我们不需要改变任何代码就可以得到新的功能。
这种方式可以便利地适应可视化对象的多变风格选择。


缺点
不幸的是,这种方式不能应用于任何对象的装饰。它基于被装饰对象的回调(如监听器)和一些其它特性。换句话说,如果被装饰对象没有合适的特征我们就不能应用外挂方式的装饰器实现。

对于可视化对象,如果我们需要改变它的paint方法,则也不能使用外挂方式,比如,如果我们需要增加RoundButton。

虽然装饰器的外挂实现是非常简单,极易上手的,而且也符合面向对象的原则。但是它取决于被装饰对象的许多特性,如监听器、边框,布局管理,以及可拨插的外观风格。

总结


讨论了三种不同类型的装饰器实现,我们可以这样比较它们:

我看不出在任何场合使用继承来实现装饰器是明智的。它看上去很简单,但是在扩展性方面存在困难,并且违背了面向对象思想。建议不要使用这种方式来实现装饰器。

封装实现装饰器的方式体现了良好的面向对象设计,也适用于那些方法不太多的类。只是对于一个很长的类,开发人员必须作出一个比较痛苦的选择:是编写大堆的代码来保持被装饰对象的原始类型?还是放弃它的原始类型为了得到精炼的代码?而且,对于可视化对象而言,这种方法在很多场合是不适用的。

外挂地实现装饰器是易于使用的,也很好地体现了面向对象的思想。但是只能用于特定的类,它们需要提供一些特征来支持外挂,如监听器、边框、布局管理器以及可拨插的外观。对于可视化的对象,它可以工作地非常良好。
分享到:
评论

相关推荐

    C#设计模式Decorate

    C#设计模式Decorate

    论文研究-融合DECORATE的异构分类器集成算法.pdf

    在基于Stacking框架下异构分类器集成方式分析的基础上, 引入同构分类器集成中改变训练样本以增强成员分类器间差异性的思想, 提出融合DECORATE的异构分类器集成算法SDE, 在1-层泛化利用DECORATE算法, 向1-层训练集...

    Python库 | decorate-0.0.13-py3-none-any.whl

    python库,解压后可用。 资源全名:decorate-0.0.13-py3-none-any.whl

    es-decorate:CoffeeScript 的 ES7 装饰器

    和等库开始根据中提出的 API 使用装饰器,Babel 5+ 和 TypeScript 1.5 支持该 API 另一方面,CoffeeScript 似乎不会很快添加装饰器: es-decorate填补了语法空白,提供了一个辅助函数,可以应用符合提议的装饰器 ...

    23种设计模式的java源代码

    里面包含了23中设计模式源代码,其中包括工厂模式(Factory)、单例(Singleton)、观察者模式(Observer)、适配器模式(Adapter)、Template模式、Decorate模式、proxy模式等 这些代码对于初学者看起来稍微复杂一些,我...

    decorate:Clojure宏,用于装饰函数

    您可以使用decorate宏将其应用于现有函数: (decorate handle-request (wrap-logging "request handler")) 这将重新定义句柄请求功能,从而保持元数据完整。 装饰库提供了四个宏: (redef name value) -重新定义...

    graphql-decorate:使用灵活的API轻松装饰graphql-ruby类型

    GraphQL装饰graphql-decorate添加了一个易于使用的界面来装饰类型。 它使您可以将逻辑从类型文件中移出并保持声明性。安装将此行添加到您的应用程序的Gemfile中: gem 'graphql-decorate' 然后执行: $ bundle或将其...

    DecoratePattern

    设计模式之装饰模式的简单代码实现(java)

    Decorate Editor Sharp-开源

    装饰编辑器Sharp是一个程序,旨在使用ZDoom的DECORATE块轻松创建自己的怪物和物品。 它是用SharpDevelop用C#编写的。 它需要.NET 2.0框架。

    angular-decorate:Javascript 装饰器 + Angular 1.x

    Angular 1.x 的 Javascript 装饰器 使用 ES6 编写 Angular 1.x 应用程序? 厌倦了声明类、添加手动注入以及使用 angular 模块注册所有内容? 不要怕! 适用于 babelJS 和理论上的 Typescript 1.5+(即将推出!) ...

    decorate-demo.zip

    装饰者模式

    Android代码-简单易用的TextView装饰库

    Decorate your TextView easily Usage TextDecorator .decorate(textView, text) .setTextColor(R.color.colorAccent, 0, 5) .setBackgroundColor(R.color.colorPrimary, 6, 11) .strikethrough(12, 26) ....

    ng-decorate:[已弃用] AngularJS 1.x的ES7装饰器

    描述Angular 1.x的ES7装饰器。 帮你: 使用ES6模块时,避免Angular的依赖项注入。 使自定义指令声明非常简短且具有语义。 直接在类属性上声明可绑定对象。 非常适合ES7 / TypeScript应用程序。 本自述您已经使用假定...

    小书匠语法说明之html属性配置decorate1

    书匠语法说明之书匠语法说明之html属性配置属性配置decorate小书匠小书匠概述概述效果疑问疑问相关相关书匠语法说明之html属性配置decorate相关疑

    23种设计模式的java源代码----容易理解的源代码

    里面包含了23中设计模式源代码,其中包括工厂模式(Factory)、单例(Singleton)、观察者模式(Observer)、适配器模式(Adapter)、Template模式、Decorate模式、proxy模式等 这些设计模式源代码更容易理解

    设计模式之装饰者模式1

    // 第10步:执行RedBalls对象的decorate方法总结装饰者模式是为已有功能动态地添加更多功能的一种方式,把每个要装饰的功能放在单独的函数里,然后用

    JAVA23种设计模式代码学习.zip

    获取到【文件夹】名称: decorate_pattern 获取到【文件夹】名称: facade_pattern 获取到【文件夹】名称: flyweight_pattern 获取到【文件夹】名称: interperter_pattern 获取到【文件夹】名称: iterator_...

    react-redux-decorate:连接React-Redux的装饰器

    装饰器的集合,使使用connect更容易。 安装 npm install react-redux-decorate 例子 import React from 'react' import { mapDispatchToProps , mapStateToProps } from 'react-redux-decorate' @ mapStateToProps...

    PyPI 官网下载 | decorate-1.0.1-py3-none-any.whl

    资源来自pypi官网。 资源全名:decorate-1.0.1-py3-none-any.whl

    学习笔记(05):Python 面试100讲(基于Python3.x)-装饰器的作用

    装饰器可以在没有任何代码变动的前提下,给一个函数增加额外功能,起到装饰作用 需求场景比如:插入日志,性能测试,事务处理,缓存等。 装饰器就是一个普通函数。 2.自定义装饰器 from functools import wraps def...

Global site tag (gtag.js) - Google Analytics