2006-7-7 19:10:37
[模式语言管理研究资料]对话Eric Gammar--如何使用设计模式
[译文]对话Eric
Gammar--设计模式的设计准则(上)
作者:oneworld
摘要
在这次对话中,Erich Gamma讨论了两个设计准则:“面向接口编程,而不是实现”和“尽量使用组合而不是继承”。
面向接口编程,而不是实现
Bill Venners: 在书的简介中,你提到可重用OO设计的两个准则。第一个是“面向接口编程,而不是实现”。他的实际意义是什么,为什么要这么做呢?
Erich Gamma: 这个准则与大型应用中需要仔细考虑的依赖关系有很大关系。增加类的依赖很容易,并且是太容易了,通常只需要增加一个引入语句。但相反的想要删除一个不想要的依赖就不是那么容易了,可能需要细致的重构甚至更困难,使你难以重用相关代码。因此你必须在引入依赖的时候多留一个心眼。这个准则告诉我们依赖于接口是更好的选择。
Bill Venners: 为什么呢?
Erich Gamma:一旦你依赖于接口,你就从实现中解耦出来了。这意味着实现可以变化,而这正是良好的依赖关系。例如,因为测试的目的你可以用轻量级的伪实现来替代重量级的数据库实现。幸运地是,现在有了重构的支持,你不再需要预先使用接口了,你可以在确实理解了问题后从具体的类中抽象出接口。有用的接口只需要通过“抽象接口”这种重构方式就可以生成了。
这种方案提供了灵活性,而且将设计从实现中分离出来,这也使得客户端不再需要与实现耦合了。这里有一个问题是是否需要用Java的接口来实现这个目的,可能抽象类是更好的选择。实际上,抽象类在演变时提供了更多的灵活性,你完全可以在不影响客户端的情况下增加新的功能。
Bill Venners: 如何来实现呢?
Erich Gamma: Java中你在接口中增加一个新方法,就影响了所有的使用者。而你使用抽象类,你可以增加一个提供默认实现的方法,所有的使用者都不爱影响。当然这里你也需要权衡一下,接口给了你不需要考虑基类的自由,抽象类给了增加新方法的自由。通常用抽象类来定义接口并不是一定可以的,随着类的演变,你还是应该考虑一下抽象类是否足够。
因为改变接口会影响使用者,那么你在发布时就应该考虑是否将他们作为不可变的。通常需要为接口增加方法时你必须增加拆分接口。在Eclipse中我们认为API的稳定性非常重要,因此你会看类似I*2这样的接口,如IMarkerResolution2和IWorkbenchPart2等。这些接口为基接口增加了方法,这么就不会影响原来接口的使用者了。然而,这会增加调用者的负担因为他们需要在运行时确定一个对象是否实现了特定的扩展接口。
另一个经验是你应该不只是关注于第一个版本,你还需要关注后续的版本。这并不是说为将来的扩展性作设计,而是要注意你必须维护产生的API并在一段时间保证其稳定性。我需要将构建持久化,这也是我们启动开发Eclipse后的一个重要风格。我们将Eclipse作为一个平台来构建,在设计Eclispe时必须考虑他可能会被使用10-20年。
在我们开始项目时,我们为基平台增加了改进的支持。其中一个例子是IAdapable接口,实现这个接口的类可以被适应至另一个接口,这也是扩展对象模式的一个实例。
Bill Venners: 有意思的是现在我们已经非常先进了,但当我们说构建的持久性时是意味着十到二十年。那么当古埃及人考虑构建持久性时他们要...
Erich Gamma: 可能是几千年吧?但对Eclispe来说,十年到二十年就够了。老实说,我也不能预见某个软件考古学家会在十或二十年后找到安装在某个硬盘上的Eclispe.我只是说Eclisep还是应该在未来十到二十内可以保持一个活跃的社区。
接口的价值
Bill Venners: A前面你说接口是非常有用的。那么他的价值表现为什么?为什么比实现更有价值?
Erich Gamma: 接口提取了对象间协作的关系。接口对实现来说是自由的,他只定义了协作的要素。一旦理解了接口,那么就理解了系统的大部分。为什么呢?因为理解了所有的接口,就理解了问题的要素。
Bill Venners: 你说的“问题的要素”指的是什么?
Erich Gamma: 方法名字是什么?抽象是什么?抽象和方面名字就定义了要素。Java的Collections包就是一个好的例子。如何用集合来工作的要素就是提取如List或Set的接口。这些接口的实现可能很多,但只要你理解了接口就理解了所有。
Bill Venners: 那么我想“面向接口编程,而不是实现”的核心是:在Java中有一种特殊的类称为接口,编程时应该在接口的基础上进行。还有一个OO的接口概念,而且每一个类都有这种概念。
如果编写代码时需要使用对象,对象的类存在于某一类层次中。在最高层次中的类是最抽象的,而在最低层次的类就是最具体的。那么我认为面向接口编程就是不要编写远离层次的类,每一个层次的类型都有相应的契约。
Erich Gamma: 是的,按照层次中的类型编程就是面向接口编程。
Bill Venners:那么要如何编写实现呢?
Erich Gamma: 想像一个有五个方法的接口,定义一个实现类实现了接口的方法并增加了另外十个方法。那么如果仅有接口被作为API发布,你调用十个方法就只是内部调用。如果你违反约定调用了十个方法中的一个,那么这种行为可能在以后会被破坏。这就是公共和发布方法的不同地方。一些方法是公共的,但并不意味着你发布了他。
这样简短的名字被用来作为API而长名字作为内部使用。当然使用私有的类和接口是另一种隐藏实现的方法。
Bill Venners: 现在我理解你的意思了。公共和发布是有区别的。
Erich Gamma: 在Eclipse中我们有对待这种区别的约定,还有工具上的支持。在Eclipse3.1中我们增加了定义发布API包定义规则的支持,这些访问规则在项目的类路径中定义。一旦你违反了定义的规则,IDE会以编译警告的方式来提示内部类的访问。例如在你增加了一个对没有发布的类的依赖时,你会得到警告。
Bill Venners: 这就是说如果我编写了调用未发布的类的接口,就是对实现编程了,而这可能会在以后被破坏。
Erich Gamma: 是的,从提供者的角度来说需要一些自由并且保留改变实现的权利。
[译文]对话Eric
Gammar--如何使用设计模式(下)
作者:oneworld
设计模式包含的核心抽象Bill Venners: 有一篇你和Kent
Beck一起写的文章“JUnit:烹饪速成”。你指出JUnit的设计是“直接从应用模式开始,一个接着一个,直到系统架构形成”。我认为这种方式可能来源于Christopher
Alexander对模式的观点,正是他的观点促进了软件模式的发展。你是否认为这种一个模式叠加另一个模式的方式是一种有效的设计方式?Erich
Gamma:
烹饪速成是一种人造的东西。我们重构了对JUnit的设计。我们并没有用这样一种模式驱动的方式来开发JUnit,相反我们遵照严格的测试驱动的方式。确实JUnit中包含很多模式实例,就像你在许多成熟设计中看到的一样。有许多你看到的关键抽象就是设计的中心,通过这些关键抽象你可以取得各种实现。因此你会看到模式以这样一种方式成长起来,但是我不会用这个作为质量的标准。
Bill Venners:
你所指的是“模式密度”吗?
Erich Gamma:
是的,模式基于一些关键的抽象。
Bill Venners:
你说在JUnit中TestCase是核心抽象。
Erich Gamma:
实际上TestCase实现了Test接口,但我们确实从TestCase开始并以此扩展开来。
Bill Venners:
那么你能定义密度吗?基于核心抽象的模式个数?你说JUnit烹饪速成是人造的。
Erich Gamma:
人造的在这里意味着烹饪速成是剥离了在我们基于测试驱动开发的过程中的测试活动后剩余的东西。因此他是缩减后的表现,只显示了基于Test的模式密度。在我们设计JUnit时并不会将模式串在一起。我们是基于测试驱动的,从建立一个我们需要的功能的测试用例开始直到通过,然后我们会看一下是否可以改善代码。虽然基于测试驱动开发测试架构不是没有这种机会,但是一但你拥有了基础就变得非常自然了,你知道Kent和我在我们设计JUnit时都非常熟悉模式。当然你可能会说“这是一种组合”。组合是JUnit中的一个模式。我们也使用模板方法,命令模式等等。我们开始测试然后说“嘿,这有是一个模板方法。这里还有一个命令模式”。因为我们精通模式,我们交流非常快,这也使设计非常快。这实际上说明了模式如何增加了我们的设计词汇。类似的,在你看UML图时,你看到的是盒子和箭头,但他们不会告诉你这些图形的意义。而一旦你了解了模式那么你就可以解释其中的关系了。例如一个观察者模式,你很清楚为什么两个类之间
有一个链接。模式提供给我们一种语言来交流设计。实际上,JUnit并不是你们想像的那样。Kent和我现在还在开发JUnit4,我们还是以测试驱动的方法来扩展他。我们开发的风格之一是减少准入的门槛,就如我们利用J2SE5的注解特性来使JUnit更容易使用一样。
模式语言
Bill Venners:
从Alexandrian思想来看,什么才是模式语言?
Erich Gamma:
Alexander有一个很大的野心就是创建一个能改善生活质量的架构。为此他开发了模式语言,这包含一系列互相构建的模式。模式语言指导设计者将单个模式集成到整个设计中。而我们开始设计模式时没有这样的野心。我们使用了基于微架构的自底向上方式。
Bill Venners: 这里自底向上指的是什么?
Erich Gamma:
让我们回顾一下来解释我是如何进入模式的。我想这样可能更容易回答你的问题。我使用ET++,这是一个复杂的类库和C++框架。当我反思ET++时,我发现他是一个成熟的框架,包含重构设计结构提供给你更多的扩展性,解耦和优雅的方案。这样的结构可以被认为是捐赠给整体系统架构的微架构。最后我在我的论文中记录了很多这样的微架构,这种方式与模式语言的形成不同:与一系列自顶向下的互相交织的模式不同,微架构更像是相互独立的自底向上的模式。模式语言指导你建立整个设计,而我们拥有的是工程知识中的片断。我承认这是缺少野心的,但这依然非常重要和有用。
Bill Venners:
是否模式语言类似自由语法的上下文,从而你可以建立整个程序?
Erich Gamma:
在某个抽象层面上是有一些相似性。就像语法可以定义一整个程序,模式语言也可以产生整个解决方案。Alexander描述模式语言的方式就是提供解决方案。因此可以被应用在许多地方。但就我来看在这个层面上就没有多少共性了。
Bill Venners:
生成是什么意思?如果我只有一个自由语法的上下文是不可能产生程序的。我还是需要编写他们。
Erich Gamma:
你确实需要做些决定,但模式语言提供给你一些指导和流程。例如你想设计一个舒适的房间,首先需要在两面墙上放置灯。你照办了,那,然后呢?你如何放置窗子?这就需要其他模式来解决了。这种方式与我们在GoF书描述的模式是不同的。我们所发现的是我们的微架构也不是孤立的。我们在书的封面描述了相关的关系,而这也是Alexander追随者为我们的书提供的仅有的有价值的东西了。
Bill Venn
ers:
这听起来像是设计方法学了。你按照这个方法,做这个,那个,然后你就会有了一个漂亮舒适的房子了。
Erich Gamma:
是的,只要你按照Alexander的模式方法,按照一定的顺序。我们不会规定特定的顺序,如果你有问题,我们会提供给你那个问题的解决方案,但我们不会说下一步怎么做。而Alexander是这样的方式。JUnit有一些这种模式语言的方式,因为他会帮你编写测试用例。在JUnit文档中,Kent和我编写了一个迷你模式语言来帮助实现测试。你从一个测试开始,然后将公共部分放到设置中,然后再将多个测试放在一起等等。
0
推荐到鲜果:


评论