EaBIM一直以来积极响应国家“十二五”推进建筑业信息化的号召,对建筑领域的信息技术开展深入技术交流和探讨!致力于打造“BIM-建筑师-生态技术”三位一体综合资源交流共享平台,希望为BIM与可持续设计理念及技术的普及做出微小的贡献!!!

萧闫子 发表于 2014-1-10 14:36:11

[设计模式] 如何使用设计模式

摘要
    在开发者中间,设计模式是思考设计问题的很受欢迎的一种方法,但是怎样才是思考设计模式的正确方法呢?在这次采访中,Erich Gamma(里程碑式的书籍《设计模式》的作者之一)和Bill Venners谈论了关于如何思考和使用设计模式的正确方法。

    Erich Gamma是在1995年作为畅销书籍《设计模式:可复用面向对象软件的基础》(Addison-Wesley, 1995) 的合著者而跃上软件业界舞台的。这项具有里程碑意义的工作,经常被援引为四人帮(GoF)的书,该书针对通常的设计问题分类整理出了23种特定的解决方案。1998年,他和Kent Beck组成团队开发JUnit,这成为Java社区事实上的单元测试工具。Gamma现在是IBM的一名杰出工程师,他在位于瑞士苏黎世的IBM Object Technology International(OTI)实验室工作。他担任Eclipse社区的领导工作,负责Eclipse平台上与Java开发相关的事务。

    2004年10月27号,Bill Venners 在加拿大温哥华举行的OOPSLA会议上遇到了Erich Gamma。在这次采访中(这次采访的内容将分多次在Artima Developer的Leading-Edge Java频道刊登出来),Gamma讲述了软件设计中深层次的东西。在这个系列的第一篇文章中,Gamma给出了他对于如何正确思考和使用设计模式的看法,并且描述了不同模式库之间的差异,比如GoF和Alexandrian的模式语言。

设计模式的真正价值

Bill Venners: Bruce Eckel 和我在教授设计模式课程,我们发现大家非常想了解四人帮(GoF)总结的那些模式。模式让讲座买的更火。围绕设计模式有很多关于市场的噱头可做。

Erich Gamma: 现在仍然这样么,已经过了10年了?

Bill Venners: 是的。大家想了解设计模式,而且我怀疑这很大程度上是因为“模式”仍然是一个时髦的词汇。我想绕过那些噱头问问你人们到底该如何对待设计模式。他们对于设计模式的态度应该是个什么样子?人们如何才能使用模式把工作做的更出色?它真正的价值到底是什么?

Erich Gamma: 我想模式作为一个整体可以帮助人们学习面向对象的思想:你如何才能用好多态(polymorphism),设计好组合(composition)和委托(delegation),平衡职责,并且提供可插拔的行为(pluggable behavior)。相对于把对象应用到绘画图形(graphical shape)的例子(有一个关于图形的类层次和一些多态的draw方法),模式要更深入一些。当你理解了模式以后,你就真正学会了多态。所以通常来说模式对于学习OO和设计是有好处的。

    在此基础上,每个单独的模式都有不同的特性可以在某些地方为你提供帮助,比如你需要更大的灵活性或者需要封装一个抽象或者需要让你的代码耦合的更松散的时候。这在大系统里确实是个大问题。如何保持你的分层?如何避免对向上的调用(up calls)或者循环依赖?GoF模式几乎没有提供什么工具帮助你解决这些问题。他们提供的帮助不是给你一个完美的解决方案而是通过解释如何权衡利弊。尽管模式是从具体应用中抽象出来的,他们仍然提供给你关于实现的有价值的线索。以我的观点来看,正是因为模式都是可实现的才使它们如此有价值。

    模式都是从牛人的经验里提取出来的。它们使你可以重复别人做过的成功的设计。这样一来你就可以站在牛人的肩膀上,用不着重新发明轮子了。但是,因为模式可以有许多实现上的变体,所以你仍然需要保持清醒的头脑。最后,由于模式提供给你用于设计的积木块的名称,所以它们也提供给你用于描述和讨论特定设计的一个词汇表。

    另一个问题是我们该如何教授模式。我并不确切知道你该如何做,但是我想你不该做的一件事是用一个类来列举23种模式。这么做并不能带来任何好处。你必须得体会到一个有问题的设计所带来的痛苦。我猜想只有感受到这种设计上的痛苦你才会认识到某个模式的重要性。

Bill Venners: 什么痛苦?

Erich Gamma: 比如说意识到你的设计不够灵活,一个小小的改动会波及到整个系统,你必须复制代码,或者代码变的越来越复杂。如果你在这样一个棘手的情况下应用某个模式,有可能会使这种痛苦消失并且后来还很舒服。当你意识到,哦,实际上这个模式,工厂模式(factory)或者策略模式(strategy),是解决我的问题的方法,你会有一种恍然大悟的感觉。我想这才是教授设计模式真正有趣的方法。

    我一开始教授设计模式的时候是非常单调乏味的,因为我只是列举那些模式。我发现试着通过实际例子激发如何应用模式会有趣的多。换句话说,你确实需要在现实背景中展示一个实际的例子——炮制出来的例子不管用。在OOPSLA的会议上我收到一本《Heads First Design Patterns》。这是一本很棒的书,不仅仅是因为它读起来很有趣,而且还因为它们能够通过一种小说似的高度可视化的方法来传达设计模式的本质。
Bill Venners: 那么,是不是说模式的价值就是,当我在现实世界中感受到某种特定的痛苦,我可以求助于某个已知的解决方案?

Erich Gamma: 毫无疑问,这是我推荐大家使用模式的方法。不要一开始就马上把模式套进某个设计,而是当你一边深入并且对问题理解更多的时候才使用它们。因为这个原因,我更愿意在犯错误之后使用模式,朝着模式的方向重构(refactoring to patterns)。在模式刚刚开始变的更加流行的时候,我在一个新闻组里看到一条评论说,有人声称他们要在一个特定的程序里尝试使用所有23种GoF模式。他们说他们失败了,因为只能用到20种。他们希望客户会再叫他们回去,这样他们可能就可以把剩下3种也加进去。

    试图使用所有的模式是不好的做法,因为你最终得到的是人工臆想出来的设计——过于深思熟虑的设计,有灵活性但却没有人需要用到。现今的软件都太复杂了。我们没功夫推测它额外要做的事情。我们需要真正聚焦在它必需做的事情上。这就是为什么我喜欢朝着模式的方向重构。人们应该学会在碰到某个特定类型的问题或者代码味道(现在大家都这么说)的时候,去他们的模式工具箱寻找一个解决方案。

Bill Venners: 真滑稽,因为我的第二个问题是,我发现人们经常觉得使用模式最多的设计是最好的设计。在我们讲授设计的讲座上,我让参与者做一个设计项目,在讲座结束的时候大家要把自己的设计讲给别人听。几乎毫无例外,那些讲述的人总是想炫耀他们在自己的设计里使用了多少模式,尽管我试图告诉他们目标是要一个干净,易于理解的API,而不是要他们赢得一个“看谁模式用的多”的比赛。我刚刚听你说了同样的事情,也就是说,这不是思考模式的正确方法。如果这种方法不对,那么在设计里使用模式的正当理由是什么呢?

Erich Gamma: 很多模式是关于扩展性和重用性的。当你确实需要扩展性的时候,模式提供给你某种方法让你实现它,而且这很酷。但是当不需要它的时候,你应该让你的设计保持简单并且不要添加不需要的抽象层。我们Eclipse的一条原则就是,我们只在要紧的地方需要扩展性。实际上,如果你对我们在Eclipse中是如何使用模式感兴趣的话,我曾经尝试把模式的应用提取出来放到《the Contributing to Eclipse》一书中的一个章节里。在这一章里,我使用设计模式解释了Eclipse架构的一些片断。

Bill Venners: 说到扩展性,您这是什么意思?

Erich Gamma: 就是说你可以定制行为,而不用触及已经存在的代码——这是OO里面很经典的一个主题。你可以重用针对特定问题调整过的某些东西。

被设计模式所包围的核心抽象

Bill Venners: 在您与Kent Beck合写的一篇叫做“JUnit: A Cook's Tour” 的文章中,你引领读者沿着JUnit的设计路线,如您自己所写的,“从零开始一个接一个的应用模式,直到得到系统的架构”。我想这种方法可能是受了Christopher Alexander的启发,他在建筑学上有关模式的工作激发了软件模式的运动。您觉得把某个模式放置在另外一个模式的层次之上,直到最后完成设计,这是一种有效的设计方法么?

Erich Gamma: “The Cook's tour”那篇文章多少有点人为炮制的痕迹。我们修整了JUnit原先的设计。然而,我们并不是以这么一种模式驱动的方法开发JUnit的,事实上,我们是严格按照测试驱动的方法来开发的。对于JUnit来说,它确实有一个针对测试的核心抽象,围绕着这个核心抽象你会看到其它几个设计要点显现出来,这几个设计要点又依次被模式实例具体化。这些东西是你在成熟设计里经常能看到的。有一些关键抽象,你会经常把他们看作一个设计的中心,围绕这些关键抽象你希望完成各种各样的事情。于是你就会看到模式从这样一个中心不断长出来。但是我不会把这个作为评判(软件)质量的标准。

Bill Venners: 这就是当您说“模式密度(pattern density)”的时候所指的东西么?

Erich Gamma: 是的,确实如此,模式围绕着某些中心抽象浮现出来的。

Bill Venners: 您说过测试用例(TestCase)是JUnit的核心抽象。

Erich Gamma: 准确的说是由测试用例实现的测试接口,但是我们确实是从测试用例开始并且以它为基础扩展的。

Bill Venners: 那么,您能给密度(density)下个定义么?是指围绕它的模式的个数么?您说过“JUnit Cook's Tour”那篇文章多少有点人为炮制的意思。

Erich Gamma: 炮制在某种意义上是说“the cooks tour”那篇文章是当你去掉在我们的测试驱动开发过程中所产生的所有测试活动所剩下的东西。所以它是一个高度压缩的陈述。密度从围绕Test固定下来了的那些模式中显现出来。

    设计JUnit的时候,我们不是仅仅把模式捆绑在一起。我们用模式驱动来做,从一个我们想要成功完成的测试开始,一旦它通过,我们会仔细看看如何能够改善那些代码。用测试驱动的方法开发一个测试框架并不是说没有没有它的挑战性,但是一旦基本的东西跑起来以后,会变得出奇的顺利。你知道,Kent和我在开发JUnit的时候对模式非常熟练。所以很自然,我们会说诸如,“嘿,那是一个嵌套模式(composite)。” Composite是JUnit用到的一个模式。我们还使用了模板方法(template method)。这是很基本的一个模式。我们用过Command模式。这当然是主要的一个。我们从测试开始并且说,“哦,这是一个command。哦,这是一个template。” 因为我们对模式都很熟练,我们的交谈进行的非常的快,这使我们可以以高速度完成一个设计。

    这实际上很好的说明了模式是如何为我们提供设计语汇的。当你看一个UML图的时候,情况也是类似的,你会看到方形和箭头,但他们没有真正告诉你在这些关系后面的含义。但是一旦你了解到那个模式,它就为你解释了这些关系是到底是干吗的。如果它是个观察者模式(observer),那在两个类之间为什么会有一个连线就很清楚了。那是因为一个家伙要观察另外一个家伙。你确切知道所发生的是什么。我猜这就是关键点。模式给了我们一种用于讨论设计的语言。实际上,JUnit之旅并没有结束,Kent和我现在还在忙于JUnit 4。我们仍然通过测试驱动的方法不断完善JUnit。我们有个想法就是要降低上手的门槛。为了达到这个目的,我们正在采用J2SE 5的中的某些功能,比如annotations,把JUnit弄得更加易于使用。

模式语言

Bill Venners: 就Alexandrian的本意来说,什么是模式语言?

Erich Gamma: Alexander有一个非常远大的目标, 他想创造出可以提升生活质量的建筑。为了完成这个目标,Alexander发展出了模式语言。这是一系列互相构建于彼此之上的模式。模式语言指导设计者把单个的模式应用到整个设计。我们开始弄设计模式的时候并没有那么远大的理想。我们使用一种以微观架构(micro-architectures)为基础的,更倾向于自底向上的方法。

Bill Venners: 您说的自底向上是什么意思?

Erich Gamma: 让我往回一点说说我是如何进入模式领域的。我想这可以回答你的问题。我曾经和Andre Weinand一道开发ET++,这是一个庞大的C++类库和框架。当我深入思考ET++的时候,一个成熟的框架显现了出来,它包括一些反复出现的设计结构的,这些设计结构能够给你很多特性,诸如扩展性,解藕,还有很重要的是——优雅。这些结构可以被认为是为整个系统架构做出贡献的微观架构。最后我在自己的论文里以书面形式整理了十几个这种微观架构。这种针对模式的方法有别于模式语言:它并不是以自顶向下的方法弄出一系列相互交织的模式,微观架构更倾向于是相互独立而最终又以自底向上的方式相互关联的模式。模式语言为你提供的知道贯穿整个设计,而我们有的只是这些碎片式的,零星的工程学知识。我承认,这种做法目标不够远大,但是它仍然很重要并且很有用。
Bill Venners: 模式语言是不是就像持有一个上下文无关的语法,你可以用它来产生一大堆程序?

Erich Gamma:从抽象的层次上来说,它们之间有某些相似性。用一个语法可以定义一大堆程序,同样的,模式语言可以产生一堆解决方案。Christopher Alexander是这么描述的,他说他的模式描述了某种解决方案,所以它就可以被多次应用而没必要每次都保持不变。但是我认为到这个层次共同性就消失了。

Bill Venners: 说到产生,他是什么意思?如果我有一个上下文无关的语法,它并不能产生程序。我仍然的把它们写出来。

Erich Gamma: 你仍然需要做决定,但是模式语言提供给你更多的指导而且它有一定的流程。比方说你想设计一个自己住着舒服的房间。他说,先在两边都放上灯。好,现在你在两边都放上了灯,下一步做什么?你如何安放窗户?针对这个问题会有其它的模式描述一个解决方案。他基本上指导你布置整个空间。这种联系正是一个模式库(比如我们在GoF那本书里描述的那些)和模式语言的区别所在。我们发现,这些微观架构实际上也不是孤岛。它们之间也发生关系。我们在那本书的封面的内页画出了它们之间的关系,而且这是Alexander的拥护者认为我们这本书唯一有价值的地方。

Bill Venners: 听起来几乎像是一个设计的方法论。你沿着这条路,一步步做下去,最后得到可以坐在里面的,漂亮而又舒服的房间。

Erich Gamma: 是的,当你遵循Alexander的模式方法的时候,你就是在按照一定顺序使用这些模式。我们并没有限制一个特定的顺序。如果你有问题,我们有相应的解决方案,但是我们没有下个步骤。我们不会给你暗示说下一步该干什么。就这种意义来说,Alexander的方法是更彻底的。JUnit采用了一点这种模式语言的方法,因为它可以帮你写一个测试用例。在JUnit的文档里,Kent和我写了一个关于如何实现一个测试的迷你型的模式语言。以一个测试作为开始,接下来你希望提取出公共的初始化代码,然后你会希望给测试分组,等等。
页: [1]
查看完整版本: [设计模式] 如何使用设计模式