Erich Gamma on Flexibility and Reuse
Copyright © 1996-2005 Artima Software, Inc. All rights reserved
http://www.artima.com/lejava/articles/reuse.html
Erich Gamma讲述灵活性和重用
与Erich Gamma的一次谈话,第二部分
Bill Venners
May 30, 2005
翻译:刘晓伟
摘要
开发者经常要面对该为软件设计多少灵活性做出抉择。在这次访谈中,Erich Gamma(里程碑式的书籍《设计模式》的作者之一)向Bill Venners谈论了他认为开发者针对灵活性和重用应该采取的态度。
Erich Gamma是在1995年作为畅销书籍《设计模式:可复用面向对象软件的基础》(Addison-Wesley, 1995) [see Resources]的合著者而跃上软件业界舞台的。这项具有里程碑意义的工作,经常被援引为四人帮(GoF)的书,该书针对通常的设计问题分类整理出了23种特定的解决方案。1998年,他和Kent Beck组成团队开发JUnit[see Resources],这成为Java社区事实上的单元测试工具。Gamma现在是IBM的一名杰出工程师,他在位于瑞士苏黎世的IBM Object Technology International(OTI)实验室工作。他担任Eclipse社区的领导工作,负责Eclipse平台[see Resources]上与Java开发相关的事务。
2004年10月27号,Bill Venners 在加拿大温哥华举行的OOPSLA会议上遇到了Erich Gamma。在这次采访中(这次采访的内容将分多次在Artima Developer的Leading-Edge Java频道刊登出来),Gamma讲述了软件设计中深层次的东西。
- 在第一部分:如何使用设计模式中, Gamma给出了他对于如何正确思考和使用设计模式的看法,并且描述了模式库(比如GoF)和Alexandrian的模式语言之间的差异。
- 在第二部分这篇文章中,Gamma谈论了重用的重要性,推测(speculating)的风险,以及 框架症(frameworkitis)所带来的问题。
重用的重要性
Bill Venners: GoF那本书的第一个句子是,“设计面向对象的软件是困难的,设计可重用的面向对象的软件更为困难。”重用到底有多重要?
Erich Gamma: 今天已经没有人会发布程序而不重复使用系统级的类库。我们的环境太复杂了,不使用它们就没法构建程序。很显然,重用是很重要的,大家就是这么做的。有个有趣的问题是,是否存在比系统级的类库更大规模的重用。多年前,当我们开始研究框架的时候,我们对此给予厚望。我们以为生产软件的方法应该是构建高层次的(high-level),专注于特定领域的框架,然后你只是定制它们并且重用所有已经被它们系统化的设计。这是重用之涅盘(nirvana)。从那时开始,我变得更加实际了,因为我认识到创建高度可重用的框架是很难的。它们会变得复杂,难以学习,而且更加难以维护。我曾经是框架的使用者,也同时是框架的创建者,从任一角度看这都很难。
框架开发所面临的一个重要挑战是如何随着时间的推移保持稳定性。一个框架走的越远,你就越发明白一开始你该如何构建它。于是,你会试着调整和改善它。然而,因为你的框架已经被大量使用,所以你所能的改变的东西就受到很大的限制。从这一点来说,至关重要的是,拥有定义完善的(well defined)API并且让用户知道什么是已发布的API,什么是内部代码。对于已发布的API你应该严格遵从稳定性,而对于内部代码你拥有改动它们的自由。
关于我在工作中是如何看待重用的,Eclipse就是一个好例子。它是由我们称之为插件(plug-in)的一堆组件搭建起来的。插件把你的代码扎成捆,还有一个清单,你可以通过它定义你扩展了哪些其它插件以及你的插件提供哪些扩展点(extension points)。插件遵循明确的约定提供可重用的代码把API和内部代码分离开来。Eclipse的组件模型是简单并且牢靠的。它有以下核心特征。Eclipse有一个小的内核,所有的事情都是通过扩展点以同样的方法完成的。组件模型和聚焦于API的这种结合是Eclipse的主要组成部分之一。受控的扩展性(controlled extensibility)是另一个重要的东西。
Bill Venners: 受控的扩展性是什么意思?
Erich Gamma: 人们告诉我们关于面向对象的第一件事情就是:“OO是很酷的方法。你可以子类化(subclass)并且定制任何东西!” 现在我认为这是对象裸露癖者的态度。你可以这么做然后暴露所有东西,而且别人也能改动任何东西。最初的Smalltalk就有点这种味道。但当下个版本出来的时候,问题就来了。如果你暴露了所有东西,你就不能够改动任何东西,否则你就打断了所有的使用者。这就是为什么在谈到重用的时候,组件模型,API聚焦以及设计好内部的和需要发布的东西,变得如此至关重要。当研究Eclipse API的时候你还会发现我们更进了一步,我们并不是仅仅指定哪些类是用于发布的API。Eclipse API还会指出某个类是否打算被子类化。这里有一个重要的教训就是,API并不仅仅是一个文档化的类。而且, API 并不是自然而然产生的; 它们需要大量的投资。
关于我们所谓受控的扩展性在Eclipse插件这个上下文中,还有另外一个重要的方面就是,扩展和扩展点。扩展点定义了你可以在哪里促成(contribute to)以及扩展某个插件。扩展点有一个名字,并且伴有一个说明,这个说明定义了当你做出贡献的时候必须遵循的接口。作为插件作者,直到考虑了你的插件可能提供的扩展点,你的工作才能算完成。
Bill Venners: 请给框架下个定义。
Erich Gamma: 我认为有三个层次上的重用。在最低层次上,你对类进行重用:类库,容器,可能还有一些类似于容器/迭代器的类“团伙”。框架属于最高的层次。它们确实是试图提取设计的精华。它们确定出用以解决某个问题的关键抽象。它们用类来表示这些关键抽象并且定义它们之间的关系。比如说,JUnit就是一个小型框架。它是框架中的“Hello, world”。我们定义了Test,TestCase,TestSuite以及它们之间的关系。典型地,框架比一个单独的类具有更大的粒度。此外,你通过在某个地方使用子类化来以挂钩的方式进入(hook into)框架。它们使用所谓的好莱坞原则“不要找我们,我们会找你。” 框架允许你定义个性化的行为,当轮到你来干些事情的时候它们会叫你。JUnit也同样如此,对么?当它想要为你执行一个测试的时候它会叫你,但是剩下的就在框架内完成了。
Bill Venners: 您说过有三种层次上的重用?
Erich Gamma: 是的,还有中间一个层次。设计模式比框架小而且抽象性更强。它们实际上是针对几个类如何能够彼此联系和相互作用的一个描述。当你从类到模式,最后到框架的时候,重用的层次就提升了。
这个中间层次很好的一点是,模式以一种比框架风险性更小的方式提供重用。构建一个框架风险很高,而且是一个很大的投资。模式允许你独立于具体代码,重用设计思想和概念。
推测(speculating)的风险性
Bill Venners: GoF那本书说,“对于最大限度的重用来说,关键在于预见到新的需求和现有需求的变更,还在于设计你的系统让它们能够相应的演化。为了设计一个针对这些变更足够健壮的系统,你必须考虑这个系统在它的生命周期中可能会需要的变动。一个没有把变化考虑在内的设计要冒着将来主要部分重新设计的风险。” 这种说法看起来和XP的理念是矛盾的。
Erich Gamma: 它确实和XP是矛盾的。它说的是要你未雨绸缪。你应该推测。你应该推测灵活性。好吧,是的,我也成熟了,而且XP提醒我们推测灵活性是划不来的,所以我可能不再会完全以这种方式书写这个问题。要添加灵活性,你必须得能够通过某个需求来证明它是正当的。如果你眼下没有这个需求,那么我不会在现在的系统里为了灵活性加入一个钩子(hook)。
但是我不认为XP和模式是冲突的。看你怎么使用模式了。XP的那些家伙们把模式放入他们的工具箱,他们只是在需要灵活性的时候朝着模式的方向重构。而我们10年前在那本书里说的是,不,你也可以推测。开始你的设计然后马上就使用它们。在最初的设计里你就使用模式,但是XP的那些家伙们不是这么做的。
Bill Venners: 如果他们不使用模式,那么XP的那些人一开始干吗?只是写代码么?
Erich Gamma: 他们写测试。
Bill Venners: 是这样,他们把测试写成代码。然后当要实现的时候,他们只是实现那些代码,让测试能够通过。接下来当他们回头再看的时候,他们重构,而且可能会实现某个模式?
Erich Gamma: 或者是当有新的需求的时候。我真正喜欢的是由需求驱动的灵活性。我们在Eclipse也是这么做的。如果需要暴露更多的API,我们在有这个要求的时候及时完成。我们是逐步的暴露API。当用户告诉我们,“哦,我必须得使用或者复制所有这些内部类。我真不愿意这么干,” 我们看到有这种需要,然后我们说,好的,我们会花时间把它发布成一个API,然后把这个当成一个承诺。所以实际上我以更小步骤考虑这个问题,我们不想在一个API被需要之前忙于把它弄出来。
Bill Venners: 但是,会存在推测出来的需求。所以如果你说你会一直等待,直到有需求出现,这难道不是在转移问题吗?因为有人可能会说,“哦,我们接下来会需要这个。” 他们说这是一个需求。但是另外有人可能会说,“噢,我们真的现在就需要这个吗?” 我不是很确定自己是否理解了什么时候该推测什么时候不该推测。谁来决定一个需求什么时候是真正的需求?作为一个程序员我该如何决断?
Erich Gamma: 嗯,你必须得有针对你生产出来的东西的客户或者消费者。对于Eclipse来说,我们的客户是写插件的这个社区。所以我们和他们交互,而且经常和他们保持同步,根据我们所知道的自己的具体应用,(推测)他们可能想要用的东西。因此我对程序员的建议是,当你必须要推测的时候,一定至少要让你的一个客户参加进来,最好是多个。如果我手头已经有了一些东西会更好一点。
Bill Venners: 您说手头有一些东西是指什么?
Erich Gamma: 我有了一个具体的实例。然后我开始推测如何才能把它弄的更加抽象,而不是其它什么方法(and not the other way around)。理想情况下,一旦我推测某些东西我立刻就会问问我潜在的用户们的反馈。
Bill Venners: 所以你会先构建一个具体的实例。
Erich Gamma: 可能是两个或者三个,直到它让我觉着不爽的时候。然后,哇,我必须再复制一次代码。这时候抽象的过程开始了。于是,我说,如果这个类能够让我以插件的形式定制行为而不必重复剩下部分,那可太棒了。
框架症(frameworkitis)的问题
Bill Venners: 框架,平台和工具箱之间的区别是什么呢?它们需要哪些不同的灵活性呢?
Erich Gamma: 对于平台来说,我把长期的稳定性和它联系在一起。在平台之上搭建东西是安全的。一个平台必须保证兼容性。框架通常没有这个品质,而且我看到过许多有关框架的缺陷都与稳定性有关。如果你看看Eclipse,是的,它包括框架,工具箱,而且还提供平台API。所有这些都捆绑成插件的形式。框架通过抽象提供较高层次的默认功能。为了达到这个目的,框架需要在我们的控制之下。如果失去这种控制可能会导致有时候所谓的框架症(frameworkitis)。
Bill Venners: 您的意思是指把所有东西都弄成框架这种病症?
Erich Gamma: 框架症是一种病症,它是指框架想要为你做太多事情或者它以某种你不想要而又无法改变的方式来做某件事情。能够自动完成所有这些功能当然很开心,但是当自动完成的这些功能妨碍到你的时候就不爽了。但是现在你已经陷入框架。为了得到期望的行为你得开始和框架作斗争。这时候通常你就开始失败了,因为很难让框架朝着并非它所期望的方向弯曲。工具箱并不试图取代你的控制权,因此它不会受到框架症的困扰。
Bill Venners: 工具箱不会受到框架症的困扰是因为。。。。。。
Erich Gamma: 使用工具箱,你创建并且调用工具箱对象,然后注册侦听者以便对事件做出响应。你自己控制这一切。框架试图得到控制权并且告诉你什么时候该干什么事情。工具箱给你积木块,但是如何控制取决于你。
Bill Venners: 也就是说框架可能有点像EJB容器,因为我是通过子类化来创建一个EJB的。
Erich Gamma: 对的。而且,如果要写框架,我们试图把它们弄成小型的框架。相对于重量级的框架,我们更倾向于多个小型的框架。
Bill Venners: 为什么?
Erich Gamma: 因为框架越大,它想越俎代庖的机会就越高,学习曲线就越陡峭,而且维护它也会更加困难。如果你真的想冒险使用框架,你应该试试小型的、焦点集中的并且有可能弄成可选的那些框架。假如你确实想用,你可以使用框架,但是你也可以使用工具箱。这种情形很好的避免了框架症所带来的问题,发生框架症的时候你会因为必须得使用框架而懊恼不已。理想情况下,我希望有一个装满了可供挑挑拣拣的小型框架的工具箱,这样在干活的时候我就能用的起框架了。
下周
6月6号,星期一,请您回来看与Erich Gamma这次谈话的下一部分。如果你想收到Artima.com上新文章每周简报的电子邮件,请订阅Artima Newsletter。
反馈
对本文中讨论的设计模式话题有自己的观点么?那么请到文章论坛里讨论这个话题, Erich Gamma on Flexibility and Reuse.
资源
[1] Erich Gamma是《设计模式:可复用面向对象软件的基础》的合著者之一,可以在Amazon.com上找到这本书 :
http://www.amazon.com/exec/obidos/ASIN/0201633612/
[2] Erich Gamma 是JUnit的作者之一,JUnit是事实上的Java单元测试标准工具:
http://www.junit.org/index.htm
[3] Erich Gamma 领导Eclipse平台上与Java开发相关的事务:
http://www.eclipse.org/
[See also] 《Contributing to Eclipse: Principles, Patterns, and Plug-Ins》, 作者是 Erich Gamma 和 Kent Beck, 这本书可以在Amazon.com上找到:
http://www.amazon.com/exec/obidos/ASIN/0321205758/
关于作者
Bill Venners 是Artima软件公司的主席兼Artima Developer的主编。他是《Inside the Java Virtual Machine》一书的作者,该书从面向程序员的角度讲述了Java平台的架构和内幕。他在JavaWorld杂志上广受欢迎的专栏覆盖了Java内幕,面向对象设计,以及Jini。从Jini启动以来,Bill就活跃在这个社区。他领导Jini社区的ServiceUI项目,这个ServiceUI API成为连接用户接口和Jini服务之间既成事实的标准方法。Bill还被选为Jini社区最初的技术监管委员会(TOC)的一员,他担任这个职务期间帮助定义了社区的管理流程。