现在的位置: 首页 > 综合 > 正文

翻译TIPatterns–模式概念(The pattern concept)

2012年07月20日 ⁄ 综合 ⁄ 共 5612字 ⁄ 字号 评论关闭

模式概念

 

“设计模式帮助你从别人的成功经验而不是你自己的失败那里学到更多东西”.

或许,面向对象设计领域迈出的最重要的一步就是“设计模式”运动,这一运动被记录整理成“设计模式”一书。那本书展示了23种针对特定类型问题的解决办法。本书将用例子来介绍设计模式的基本概念。这将会激起你阅读Gamma等人所著的《设计模式》一书的兴趣,该书已成为从事面相对象编程的程序员的重要的和几乎必需的“词汇表”。

本书后面的章节用一个例子描述了设计演化的过程,从最初的解决方案开始,按照合理的推理和步骤,最终演化成更为合理的设计。这个例子程序(一个模拟trash sorting 的例子程序)随着时间不断演化,你可以把它看作是一个原型,你自己做设计的时候也是从一个适用于某一特定问题的解决方案开始,逐渐演化成一个灵活的能够解决某一类问题的方法。

 

什么是模式?

一开始,你可以把模式想象成一种特别巧妙和敏锐的用以解决某类特定问题的方法。更确切地说,许多人从不同角度解决了某个问题,最终大家提出了最通用和灵活的解决办法。这个问题可能是你以前见过并解决过的,但是你的方法可能比不上你将看到的模式所体现的方法来的完整。

尽管它们被称作“设计模式”,实际上它们没有仅仅限于设计的范畴。模式看起来似乎跟传统的分析,设计和实现相去甚远;恰恰相反,模式体现的是程序整体的构思,所以有时候它也会出现在分析或者是概要设计阶段。这是个有趣的现象,因为模式可以由代码直接实现,所以你可能不希望在详细设计或编码以前使用模式,(实际上在详细设计和编码之前你可能都不会意识到你需要某个特定的模式)。

模式的基本概念也可以看作是设计的基本概念:即增加一个抽象层。无论什么时候,当你想把某些东西抽象出来的时候,实际上你是在分离特定的细节,这么做的一个有说服力的动机就是把变化的东西从那些不变的东西里分离出来。这个问题的另一种说法是,当你发现程序的某一部分由于某种原因有可能会变化的话,你会希望这些变化不会传播给程序代码的其它部分。这么做不但使程序更容易维护,而且它通常使程序更容易理解(这将降低成本)。

很多情况下,对于能否设计出优雅和容易维护的系统来说,最难的就是找到“一系列变化的东西。”(这里,“vector”指最大梯度――变化最大的部分-―而不是指容器类。)这就意味着最重要的是找出你的系统里变化的部分,或者说是找到成本最高的部分一旦你找出了这一系列变化,你就可以以之为焦点来构造你的设计。

设计模式的目的就是为了把代码里变化的那一部分分离出来。如果你这么看待设计模式,你会发现本书实际上已经讲了一些设计模式了。比如说,你可以认为继承(inheritance)就是一种设计模式(只不过他是由编译器来实现罢了)。通过继承你可以使拥有相同接口(这些接口是不变的)的对象具有不通的行为(这就是变化的部分),组合(composition)也可以被认为使一种模式,它可以静态或者动态的改变你用以实现某个类的对象,从而改变这个类的行为。

《设计模式》这本书里讲的另外一个模式:迭代器(iterator),你也已经见到过了。(Java1.01.1自以为是的把它叫做枚举器(Enumeration);而Java 2 的容器类用了“迭代器(iterator)这个术语”)。这种方法在你需要遍历和选定某些元素的时候隐藏了容器类的特定实现。通过使用迭代器,你可以写出针对某一序列元素的泛型(generic)代码,而不用管这一序列元素是如何生成的。这样,这些泛型代码就可以用于所有能产生迭代器的容器类了。

 

模式范畴 pattern taxonomy

设计模式兴起以后,有人担心“设计模式”这个词被滥用,因为人们开始把所有它们觉得好的东西都说成是设计模式。慎重考虑之后,我把这一系列相近的东西区分为一下几个范畴:

  1. 惯用法(Idiom):如何针对某种特定的语言,为了实现某种特定的功能,书写代码。比如说,就像你如何在C里面遍历一个数组(你不会一下子就跳到数组的最后一个元素)。

  2. 特定设计Specific Design):为了解决某一特定问题,所提出的某一方法。它可能是一个很巧妙的设计,但是我们并不指望它成为通用的解决方案。

  3. 标准设计 standard design):为了解决某一类问题,所提出的更为通用的一种设计方案,通常通过复用(reuse)来满足它的通用性。

  4. 设计模式 (Design pattern):用以解决某一大类相似的问题。通常是我们多次应用某一标准设计后,发现某一通用模式可以从这些应用里抽象出来。

我觉得这么区分可以帮助我们以更全面的眼光来看待这些范畴,从而找到它们适用的地方。但是,这并不意味着某一个范畴就比另外一个好。试图把每一个解决方案都泛化(generalize)成设计模式是没有意义的,这么做纯粹是浪费时间而且也不大可能发现(新的)模式。模式通常是随着时间的推移在不经意间产生的。

上面那些范畴的分类里还可以再加入分析模式(Analysis Pattern)和架构模式(Architectural Pattern)。

 

设计原则

(从幻灯片里搬到这儿来了)

当我通过自己的电子通讯(newsletter)征集观点的时候,很多人会给我许多有用的建议,但是它们不同于上面讨论的那些范畴。我意识到,一个有关设计原则的列表至少是和设计构造同等重要的。但是,还有另外一个原因,有了这些设计原则你就可以用它们来对自己所建议的设计提出质疑,并检验这些设计的好坏。

  • 尽量少使奇巧淫技 (不要对这条原则感到奇怪)Principle of least astonishment (don’t be astonishing).

  • 使一般问题简单化,罕见问题行得通(rare things possible

  • 一致性(consistency)。这个对我来说已经很清楚,尤其是有了Python以后:如果你强加给程序员越多条条框框,而这些条条框框对于解决手头问题没多大用处,程序员编程的效率就越低。而且这个幅面影响不是线性增长的,而是成几何级数增长的。

  • 得墨忒耳(Demeter)法则:“不要跟陌生人讲话。”一个对象最好只引用它自己的属性和它自己方法的参数。

  • 减法(Subtraction)原则:当你不能从一个设计里去掉任何东西的时候,这个设计就算完成了。

  • 简单(Simplicity)比通用性(generality)重要。(这是奥卡姆剃刀原则(Occam’s Razor)的另一种说法。它的原话是“最简单的就是最好的”)。一个常见的问题是,许多框架在设计的时候总是一味强调通用性而丝毫没有考虑到实际系统。这导致一大堆使人眼花缭乱的选项,而这些选项要么经常用不到,要么被误用或者根本就没什么用处。而且,大多数开发人员开发的都是专用系统,很多时候寻求通用性对他们来说并没有太大用处。寻求通用性最好的做法是通过理解现有的完善成熟的特定例子。因此,这条准则使得在简单设计和通用设计都可行的情况下选择简单设计。当然,简单设计正好就是一个通用设计也是完全可能的。

  • 自反性(Reflexivity)(我推荐这个术语)。每个类一个抽象,每个抽象对应一个类。这个准则也可以称作同构(isomorphism)。

  • 独立性(Independence),或者叫正交性(Orthogonality)。独立表达每一个单独的想法。这条准则是分离(Separation),封装(Encapsulation)和变化(Variation)的补充。它是“低耦合高内聚(Low-Coupling-High-Cohesion)”思想的一部分。

  • 一次且只能出现一次(Once and once only):如果两段代码干的是同一件事情,应该避免逻辑上和结构上的重复。

最初思考这些设计原则的的时候,我只是希望总结出一两条以便你在分析一个问题的时候直接就能用上然而,你可以用上面列表里的其它准则作为对照表来检查和分析你的设计。

 

模式分类

《设计模式》讨论了23种不同的模式,按照目的把它们分成三类(这些目的围绕某一可变化的特定情况)。这3种分类分别是:

  1. 创建(Creational)型:如何创建一个对象。这通常包括分离对象创建的细节,这样你的代码就不依赖于对象的类型,而且当加入新的类型的对象时也不必改代码。下面将要讲到的单件(Singleton)模式就是一种创建型模式,在本书稍后的章节你还会看到工厂方法(Factory Method)和原型(Prototype)模式的例子。

  2. 结构(structural)型:设计出满足某些工程里特定约束的对象。它的工作原理是这样的:一组对象与其它对象相关联,当系统发生变化的时候,这些对象之间的关联关系不变。

  3. 行为(Behavioral)型:指一个程序里处理一系列特定类型操作的对象。它们封装了一系列操作,比如某种语言的解释(interpreting a language),填表单,遍历一个序列(像迭代器(iterator)那样),或者实现了某种算法。本书以观察者模式(Observer)和访问者模式(Visitor)来举例说明。

《设计模式》专门有一部分用一个或多个例子来说明这23个模式,这些例子基本上都是用C++写的(rather restricted C++, at that),有时候也用Smalltalk。(你会发现这并不是个大问题,因为你可以好不费力的把那些概念从C++Smalltalk转换到Java)。本书将用面向Java的思想重新讲解《设计模式》里的许多模式,因为更换语言带来了对模式表示方法和理解的变化。但是,这里不会重复GOF的那些例子,因为我相信通过努力,我们可以设计出更容易让人理解的例子。我的目标是使你知道模式是干什么用的并且为什么它们这么重要。

多年来接触这些东西,我逐渐意识到模式本身用到的其实都是很基本的组织结构(structure),而不是像《设计模式》里所描述的那么复杂(比那些要简单简单的多)。我之所以这么说是因为,大多说设计模式(不限于《设计模式》所总结的那些),就其实现(implementation)所应用的结构(structure)而言,是存在很大的相似性的。尽管我们总是试图避免使用实现(implementation),取而代之以接口(interface),但在这里,我认为用“结构要素”(structural principles)来讲解,会使大家更容易理解这些模式。我试图以这些模式结构上的相似性来给它们重新归类,而不采用《设计模式》里的那些分类。

然而,后来我发现,根据这些模式所解决的问题来给他们归类会更有用。我期望,这种归类方法与Metsker在《Java设计模式手册》( Design Patterns Java Workshop (Addison-Wesley 2002))里所提出的根据目的(intent)来归类的方法会有所不同,这种不同虽然微妙但却是重大的。如果说模式都是用我说的这种方法归类的的话,我希望读者(在学完本书后)能够辨别出问题所在并找到一个解决方案。

在不断“重构本书(book refactoring)”的过程中,我意识到如果我更改过某个地方,那么很有可能以后我还会改那个地方(当然会有一个次数限制),所以我去掉了所有的章节号索引,这样便于以后的改动(这就是鲜为人知的“无章节号” 模式(numberless chapter pattern)):

 

开发所面临的挑战(challenge

 

关于程序开发,UML过程,极限编程(Extreme Programming)。

评估有价值么(Is evaluation valuable)?能力成熟度模型(The Capability Immaturity Model):

Wiki Page: http://c2.com/cgi-bin/wiki?CapabilityImMaturityModel

Article: http://www.embedded.com/98/9807br.htm

关于成对编程的研究(Pair programming research):

http://collaboration.csc.ncsu.edu/laurie/

 

 

单元测试

在本书的早些时候的一个版本里,我认定单元测试是必不可少的(和我其它所有的书一样),但是那时候的JUnit用起来实在是太不爽了。当时,我自己写了一套单元测试的框架,它使用Java的反射(reflection)技术使单元测试所必需的一些语法得以简化。

Thinking in Java的第三版里,我开发了另外一套单元测试的框架用以测试那本书中例子的输出结果。

    在此期间,JUnit 增加了支持单元测试的语法,这些语法跟我在本书以前版本里用的那些如出一辙。我不知道自己对(JUnit)那些改动起到了多大的影响,但是我很高兴JUnit作了这些改动,这样我就不用维护我自己的测试框架了(但你还是可以在这儿找到它),我所需要做的只是向大家推荐这个既成事实的标准(指JUnit)。

我在Thinking in Java 第三版第十五章里介绍和描述了JUnit的编程风格,我认为那些代码是“最好的实践”(最主要是因为它们很简单)。与本书有关的单元测试,那一章里提供了足够多的介绍(但是,本书通常不会在正文里包含单元测试的代码)。当你下载本书配套代码的时候,你会发现,绝大多数情况下那些代码都是包含单元测试的。

 

测试代码的位置

From Bill)这些都是啥玩艺?是不是还没写完?

公有的(public):在“test”子目录里;不通的package(不要包含在jar里)。

享有Package 权限(Package access)的:同一个package,一个库里的子目录(不要包含在jar里)。

私有权限(Private access)的:(白盒测试)。嵌套类,strip out,或者JUnit插件(addons)。

 

 

目录

 

【上篇】
【下篇】

抱歉!评论已关闭.