论坛首页 综合技术论坛

设计模式与XP

浏览 20292 次
该帖已经被评为精华帖
作者 正文
   发表时间:2004-02-10  
转自CSDN

设计模式与XP 
Joshua Kerievsky 著,Gigix 译
概述
模式和极端编程(XP )都为软件设计、开发者提供了无法用金钱衡量的帮助。但是迄今为止XP 大量关注于重构(refactoring ),而对模式只字不提。在这篇文章中,我问“为什么”,并且最终描述出模式怎样以XP 的方式更好地实现、以及XP 怎样因为包含对模式的使用而变得更好。


致谢


非常感谢Kent Beck 、Martin Fowler 和Ward Cunningham ,他们为这篇文章提出了友善的评论。
仍在所知不多的时候我们就开始了自己的程序设计生涯,生产出的软件也反映出了我们的缺乏经验:
我们创建的代码臃肿、错误百出、脆弱、难以维护、难以扩展。随着时间的流逝,我们成为了更好的软件设计者:我们从技术作家、专家那里学习,我们从自己的错误中学习。现在我们编写具有高度灵活性的软件,它适应广泛而且坚固。当被请求编写一个新的系统时,我们知道查明当前和将来的需求,这样我们可以设计软件来处理当前和将来的需要。
在软件开发生涯的这个阶段,极端编程告诉我们,我们经常对软件过分设计(over-engineer )了。我 们从自己的错误中学到了太多,我们不希望重复这些错误,所以我们在系统生命周期的早期做了大量的努力来创造灵活而坚固的设计。不幸的是,我们没有认识到:如果这个系统永远不需要这个程度的灵活性和 坚固性,那么我们所有的工作就都没有意义了。我们过分设计了。 


我也曾经过分设计过。说实话,与其他设计者坐在一间房间里考虑如何设计软件来适应许多当前和将来的需求,这的确是一种乐趣。我们把自己学到的所有东西——尤其是那些最好的经验——应用在设计中。
我们常常知道需求的列表会改变,但用户或客户总是改变需求。不过,我们认为我们可以足够聪明地设计软件,使软件足够灵活,使它能应付所有的需求变化。
典型的过分设计。
今天,极端编程将告诉你这是多么愚蠢的做法。XP 说,我们必须让设计自己显现出来,而不是去预测设计将是什么样子。XP 说,“做可能起作用的最简单的事”,因为“你将不再需要它你将不再需要它你将不再需要它你将不再需要它”。另外,Kent Beck
说:
你需要在一个强调沟通、简单、反馈和勇气的价值系统中选择最好的工作方法,这样你才能你需要在一个强调沟通、简单、反馈和勇气的价值系统中选择最好的工作方法,这样你才能你需要在一个强调沟通、简单、反馈和勇气的价值系统中选择最好的工作方法,这样你才能你需要在一个强调沟通、简单、反馈和勇气的价值系统中选择最好的工作方法,这样你才能勇敢的脱离过分设计。勇敢的脱离过分设计。勇敢的脱离过分设计。勇敢的脱离过分设计。Beck1 00?
同意。但是,现在我必须提到我的朋友Norm Kerth 。Norm 在软件开发领域有丰富的经验和见识。一年 以前我问他“对XP 有什么想法”。他说:
我喜欢我喜欢我喜欢我喜欢XP 里的每样东西。我关心的是:还有什么不在里的每样东西。Kerth 99?
当时,我只认为Norm 是一个保守派。但现在我不能确定了。XP 明显缺少的就是使用模式的经验。尽管一些XP 的创始人帮助建设了模式社团,但没有哪一个坚定清楚的说明模式如何适应XP 。
一开始,这还没有让我感到迷惑。但现在,我的确感到迷惑。
我感到迷惑,因为我在XP 和模式上的经验让我相信:在XP 的场景中模式会工作得更好;并且当XP 包
含模式时,XP 也会工作得更好。
这需要一些解释。我将从描述我自己使用模式和XP 的一些经验开始。
从1995 年开始,我开始沉浸入模式之中。我学习模式文献、主办了一个每周一次的模式学习组、使用模式设计和开发软件、并进行UP (一个关于使用模式的国际学术会议)的组织和运转工作。说我“热衷于 模式”实在是一种保守的说法。
当时,就象很多第一次学习模式的人一样,我有一点过分渴望使用它们。这不是一件好事,因为它会让你的设计比需要的更复杂。但我没有意识到这一点,直到我开始学习重构。
大概在1996 年,我第一次接触到了重构。我开始实证它并很快观察到重构带我离开了我在模式学习中学到的某些原则。
举个例子,那本里程碑式的书——《设计模式:可复用面向对象软件的基础》中的一个原则是:
针对接口编程,而不是针对实现编程。针对接口编程,而不是针对实现编程。GHJV1 95?
《设计模式》的作者们做了相当精彩的工作来解释为什么我们需要遵循这条建议。几乎在所有的模式中,都讨论了当你针对某个特定实现编程时你的软件如何变得缺少灵活性和可修改性。几乎每一次都是接口过来帮忙。
但如果我们不需要灵活性和可修改性,情况又是怎样?为什么我们要在开始设计时预料一些可能永远 不会出现的需要?这是我的一次觉悟。所以随后我记录下了下面这个J AVA 技巧:
不要分离类和接口不要分离类和接口不要分离类和接口不要分离类和接口
我曾经习惯于在我的接口名字后面加上一个“I ”。但当我继续学习更多的重构技术时,我开始看到一 种明智的做法:把类名和接口名设计成一样。下面是原因:在开发过程中,你知道你可以使用一个接口来 让某些东西变得灵活(使实现多样化),但可能你现在根本不需要让实现多样化。所以,放下预测太多的“过分设计”吧,你仍然保持简单,仍然把东西放在一个类中。在某个地方你会写一个方法语句来使用这 个类的对象。然后,几天、几星期、几个月之后,你明确“需要”一个接口。因此你就将原来的类转换成
一个接口,再创建一个实现类(实现新的接口),并且让你原来的语句保持不变。 Kerievsky 96?
我继续学习类似于重构的课程,逐渐的,我使用模式的方式开始改变了。我不再预先考虑使用模式。
现在,我更加明智了:如果某个模式可以解决某个设计问题,如果它提供一种方法来实现一个需求,我就 会使用它,但我将从可以编码出的模式的最简单实现开始。晚些时候,当我需要增加或修改时,我将让这 个实现更加灵活、稳固。
这种使用模式的新方法是一种更好的方法。它节约了我的时间,并让我的设计更简单。
由于我继续学到更多关于XP 的知识,我很快开始考虑这样一个事实:那些清楚介绍“XP 是什么”和“XP 如何工作”的人毫不提及模式。看起来,焦点已经全部从开发转向了重构。构造一点,测试一点,重 构一点,然后再重复。
那么,模式怎么了?
我收到的一般的答案是:模式鼓励过分设计,而重构保持事情简单、轻量级。
现在,我和其他任何人一样喜欢重构——我回顾了Martin Fowler 的书的关于这个主题的两份手稿,然 后知道重构将成为一个标准。但我仍然喜欢模式,我发现模式在“帮助人们学会如何设计更好的软件”方 面是无价之宝。所以,XP 怎么能不包括模式呢?!
我小心的在Portland Pattern Repository 上写下了我的不安。我问:是否完美的XP 模式应该由完全不知 道模式的程序员和指导者组成,是否他们应该完全依赖重构来“让代码去它该去的地方”。Ron Jeffries ,世
界上最有经验的XP 实践者,与我争论了这个主题,并且这样写:
一个初学者不能倾听代码所说的话。他需要学习代码质量的模式(在一般意义上)。他需要一个初学者不能倾听代码所说的话。他需要学习代码质量的模式(在一般意义上)。他需要一个初学者不能倾听代码所说的话。他需要学习代码质量的模式(在一般意义上)。他需要一个初学者不能倾听代码所说的话。他需要学习代码质量的模式(在一般意义上)。他需要
看好的代码(以及,我猜,差的代码),这样他才能学会写出好的代码。
一个问题,我的意思是一个可能的问题是,现在的模式是否被用于帮助提高代码的质。我想Beck 的Smalltalk Best Practice Patterns 会有帮助,因为那些都是非常小型的模式。我想会有帮助,因为那些都是非常小型的模式。设计模式都更值得怀疑,因为模式和讨论有时变得相当大,而且它们可能造成看起来合理的庞大设计模式都更值得怀疑解决方案。Martin Fowler 的精彩的分析模式也有同样的危险:在可以选择的精彩的分析模式也有同样的危险:在可以选择一个小规模解决方案的时候选择了大规模的解决方案。Jeffries 99?
一个非常有趣的关于模式的观点。尽管我已经看到模式可以被明智的实现、使用,但Ron 看起来却认
为它们是危险的,因为它们“让庞大的解决方案看起来合理”。在其他地方,Ron 观察了一件经常发生的事
情:第一次学习模式的人们如何过分渴望使用它们。
我无法不同意后面这个观察结果。就象任何新事物——甚至是XP ——一样,人们可能会过分渴望使
用它们。但模式真的鼓励在可以使用小规模解决方案时使用大规模解决方案吗?
我想这主要取决于你如何定义、使用模式。举个例子,我观察了许多模式的初级使用者,他们认为一
个模式与它的结构图(或类图)是完全相同的。只有在我向他们指出“模式可以根据需要以不同的方式实
现”之后,他们才开始发现这些图只是表示实现模式的一种方式。
模式的实现有简单的也有复杂的。诀窍是:发现模式针对的问题,将这个问题与你当前的问题进行比 较,然后将这个模式最简单的实现(解决方案)与你的问题进行比较。当你这样做时,你就不会在可以使 用小规模解决方案的时候使用大规模解决方案。你获得了解决问题最好的平衡。
当人们没有受过模式的良好训练时,困难就可能出现。Ron 提到人们使用模式的方式是“现在构成的”
——这就是说,他们如何与现在的作者沟通。我同意模式文献有一些缺点。关于模式的书很多,你可以花 一些时间来理解模式解决的问题,这样你就可以聪明的根据自己的特定需要选择模式。
这种选择是极其重要的。如果你选择了错误的模式,你可能过分设计或仅仅把你的设计揉在一起。有 经验的模式使用者也会犯错误,并且经常看到这样的结果。但这些专家有其他的模式作为装备,这些模式 可以帮助他们面对自己的错误。所以他们最终经常把自己真正需要的模式换成了不那么理想的模式。
那么,你将怎样成为一个有经验的模式使用者呢?我发现除非人们投身于大量模式的学习中,否则他 们就有可能陷入误解它们、过分使用它们以及用它们过分设计的危险之中。
但这是避免使用模式的一个原因吗?
我想,不。我发现模式在如此多的项目中如此有用,以至于我无法想象不使用它们来进行软件设计和 开发。我相信对模式的彻底的学习是非常值得的。
那么,XP 对模式保持沉默是因为感觉到它们将被误用吗?
如果情况是这样,也许问题已经变成:我们怎样使用模式中的智慧,而避免模式在我们怎样使用模式中的智慧,而避免模式在我们怎样使用模式中的智慧,而避免模式在我们怎样使用模式中的智慧,而避免模式在XP 开发场景中的开发场景中的开发场景中的开发场景中的 误用呢?误用呢?误用呢?误用呢?
在这里,我想我必须回到《设计模式》。在“结论”一章、“设计模式将带来什么”一节、“重构的目标”小节中,作者写道:
我们的设计模式记录了许多重构产生的设计结构。在设计初期使用这些模式可以防止以我们的设计模式记录了许多重构产生的设计结构。不过即使是在系统建成之后才了解如何使用这些模式,它们仍可以教你如何修改你的后的重构。不过即使是在系统建成之后才了解如何使用这些模式,它们仍可以教你如何修改你的后的重构。设计模式为你的重构提供了目标。GHJV2 95?
这就是我们需要的观点:重构的目标。这就是重构和模式之间的桥梁。它完美的描述了我自己在如何 使用模式方面的进步:从简单开始,考虑模式但将它们保持在次要地位,小规模重构,只有在真正需要模 式的时候才把重构转移为模式。
这个需要训练和仔细判断的过程将很好的适应XP 所包含的最好的习惯。
而且这个途径很明显与“故意不知道或不使用模式而只依赖重构来改善设计”的方法非常不同。
只依赖重构的危险是:没有目标,人们可能使设计小小进步,但他们的全面设计将最终受损害,因为 这种方法缺乏顺序、简单性和效力,而聪明的使用模式则可以让开发者拥有这些。
引用Kent Beck 自己的话:模式生成体系结构模式生成体系结构模式生成体系结构模式生成体系结构。Beck2 94?
但模式不保证有纪律的使用。如果我们在设计中过多、过早的使用它们,我们就又回到了过分设计的 问题。因此,我们必须回答这个问题:“在设计的生命周期中,何时引入模式是安全的?”请回忆上面对 《设计模式》的引用:
在设计初期使用这些模式可以防止以后的重构。在设计初期使用这些模式可以防止以后的重构。在设计初期使用这些模式可以防止以后的重构。在设计初期使用这些模式可以防止以后的重构。
这是一个聪明的主张。如果我们不知道“何时配置一个模式”的基本规则,那么我们就很容易在设计 周期的早期就陷入过分设计。
再一次,问题又全部集中在一起:如何将项目中的问题与一个合适的模式相匹配。
在这里,我必须讲述我为不同行业开发软件得到的经验。
有一家客户要求我和我的团队用J AVA 为他们的网站构造软件,这将是一个很酷的交互式版本。这个 客户没有任何J AVA 程序员,但仍然要求能在他们需要的任何时候、任何地方修改软件的行为,而不必做程 序的修改。多么高的要求!
在对他们的需要做了一些分析之后,我们发现Command 模式将在这个设计中扮演一个非常重要的角 色。我们将编写命令对象,并让这些命令对象控制软件的整个行为。用户将可以参数化这些命令、将它们 排序、并选择命令运行的时间和地点。
这个解决方案工作得很完美,Command 模式正是成功的关键。所以在这里,我们不会等到重构的时候 才使用Command 模式。相反,我们预先看到了使用它的需要,并从一开始就用Command 模式来设计软件。
在另一个项目中,系统需要作为独立应用程序和WEB 应用程序运行。Builder 模式在这个系统中发挥了 巨大的作用。如果没有它,我不敢想象我们会拼凑出一个多么臃肿的设计。Builder 模式的作用就是解决“多平台、多环境运行”这样的问题。所以在设计早期就选择它是正确的。
现在,我必须声明:即使在设计的早期引入了模式,但一开始仍然应该按照它们最原始的样子来实现它们。只有在晚些时候,当需要附加的功能时,模式的实现才能被替换或升级。
一个例子会让你更清楚这一点。
上面提到的由命令对象控制的软件是用多线程的代码实现的。有时候两个线程会使用同一个宏命令来 运行一系列命令。但一开始我们并没有被宏命令的线程安全问题困扰。所以,当我们开始遇到线程安全造 成的莫名其妙的问题时,我们必须重新考虑我们的实现。问题是,我们应该花时间构造宏命令的线程安全 吗?或者有没有更简单的方法来解决这个问题?
我们用更简单的方法解决了这个问题,并且避免了过分设计:为每个线程提供一个独立的宏命令实例。
我们可以在30 秒内实现这个解决方案。请把这个时间与设计一个线程安全的宏命令所需的时间做一下比 较。
这个例子描述了XP 的哲学怎样在使用模式的情况下保持事情简单。没有这种简单化的驱动,过分设 计的解决方案——就象线程安全的宏命令——很容易出现。
因此,简单化和模式之间的关联是很重要的。
当程序员需要做出设计决策时,很重要的一件事就是:他们应该试图保持设计简单,因为简单的设计 通常比庞大而复杂的设计更容易维护和扩展。我们已经知道,重构意味着将我们保持在简单的路上:它鼓 励我们以小而简单步骤逐渐改进我们的设计,并避免过分设计。
但是模式呢?难道它们不是帮助我们保持简单吗?
有些人会说“不”。他们认为模式尽管有用,但容易造成复杂的设计。他们认为模式会造成对象快速 增加,并导致过分依赖对象组合。
这种观点是由于对使用模式的方法的错误理解。有经验的模式使用者会避免复杂的设计、对象的快速 增长和过多的对象组合。
实际上,在使用模式的时候,有经验的模式使用者会让他们的设计更简单。我将再用一个例子来说明 我的观点。
JUnit 是一个简单而有用的J AVA 测试框架,它的作者是Kent Beck 和Erich Gamma 。这是一个精彩的软件,其中满是精心选择的简单的模式。
最近一些人要求我对JUnit 进行DeGo?F ,也就是说,将JUnit 中的设计模式移除掉,以观察没有模式的JUnit 是什么样子。这是一次非常有趣的练习,因为它让参与者认真考虑应该在什么时候在系统中引入模式。
为了描述他们学到的东西,我们将对JUnit 2.1 版中的一些扩展进行DeGo?F 。
JUnit 中有一个叫做Test Case 的抽象类,所有的具体测试类都派生自它。TestCase? 类没有提供任何多次运行的方法,也没有提供在自己的线程中运行测试的方法。Erich 和Kent 用Decorator 模式很优雅的实现了可重复测试和基于线程的测试。但是如果设计团队不知道Decorator 模式呢?让我们看看他们会开发出什么, 并评估一下他们的设计有多简单。
这是Test Case 在JUnit 框架1.0 版本中的样子(为了简化,我们忽略了注释和很多方法):

--------------------------------------------------------------------------------1
2 public abstract class TestCase implements Test {
3 private String fName;
4 public TestCase(String name) {
5 fName= name;
6 }
7 public void run(TestResult result) {
8 result.startTest(this);
9 setUp();
10 try {
11 runTest();
12 }
13 catch (AssertionFailedError e) {
14 result.addFailure(this, e);
15 }
16 catch (Throwable e) {
17 result.addError(this, e);
18 } tearDown();
19 result.endTest(this);
20 }
21 public TestResult run() {
22 TestResult result= defaultResult();
23 run(result);
24 return result;
25 }
26 protected void runTest() throws Throwable {
27 Method runMethod= null;
28 try {
29 runMethod= getClass().getMethod(fName, new Class[0]);
30 } catch (NoSuchMethodException e) {
31 e.fillInStackTrace();
32 throw e;
33 }
34 try {
35 runMethod.invoke(this, new Class[0]);
36 }
37 catch (InvocationTargetException e) {
38 e.fillInStackTrace();
39 throw e.getTargetException();
40 }
41 catch (IllegalAccessException e) {
42 e.fillInStackTrace();
43 throw e;
44 }
45 }
46 public int countTestCases() {
47 return 1;
48 }
49 }
--------------------------------------------------------------------------------

新的需求要求允许测试重复进行、或在它们各自的线程中进行、或以上两者。
没有经验的程序员通常在遇到这样的新需求时进行子类型化。但是在这里,因为知道TestCase? 对象将需要能够在同一个线程中重复运行、或在各自独立的线程中重复运行,所以程序员知道:他们需要考虑得更多。
一种实现方法是:将所有的功能都添加给TestCase? 本身。许多开发者——尤其是那些不了解设计模式的开发者——将会这样做,而不考虑这会使他们的类变得臃肿。他们必须添加功能,所以他们将功能添加到任何可以添加的地方。下面的代码可能就是他们的实现:

--------------------------------------------------------------------------------1
2 public abstract class TestCase implements Test {
3 private String fName;
4 private int fRepeatTimes;
5 public TestCase(String name) {
6 this(name, 0);
7 }
8 public TestCase(String name, int repeatTimes) {
9 fName = name;
10 fRepeatTimes = repeatTimes;
11 }
12 public void run(TestResult result) {
13 for (int i=0; i < fRepeatTimes; i++) {
14 result.startTest(this);
15 setUp();
16 try {
17 runTest();
18 }
19 catch (AssertionFailedError e) {
20 result.addFailure(this, e);
21 }
22 catch (Throwable e) {
23 result.addError(this, e);
24 }
25 tearDown();
26 result.endTest(this);
27 }
28 }
29 public int countTestCases() {
30 return fRepeatTimes;
31 }
32 }
--------------------------------------------------------------------------------

请注意run (TestResult? result )方法变大了一些。他们还为TestCase? 类添加了另外的构造子。到目前为 止,这还不算什么大事。并且,我们可以说:如果这就是所有必须做的事情,那么使用Decorator 模式就是多余的。
现在,如果要让每个TestCase? 对象在其自己的线程中运行又怎样呢?这里也有一个可能的实现:

--------------------------------------------------------------------------------1
2 public abstract class TestCase implements Test {
3 private String fName;
4 private int fRepeatTimes;
5 private boolean fThreaded;
6 public TestCase(String name) {
7 this(name, 0, false);
8 }
9 public TestCase(String name, int repeatTimes) {
10 this(name, repeatTimes, false);
11 }
12 public TestCase(String name, int repeatTimes, boolean threaded) {
13 fName = name;
14 fRepeatTimes = repeatTimes;
15 fThreaded = threaded;
16 }
17 public void run(TestResult result) {
18 if (fThreaded) {
19 final TestResult finalResult= result;
20 final Test thisTest = this;
21 Thread t= new Thread() {
22 public void run() {
23 for (int i=0; i < fRepeatTimes; i++) {
24 finalResult.startTest(thisTest);
25 setUp();
26 try {
27 runTest();
28 }
29 catch (AssertionFailedError e) {
30 finalResult.addFailure(thisTest, e);
31 }
32 catch (Throwable e) {
33 finalResult.addError(thisTest, e);
34 } tearDown();
35 finalResult.endTest(thisTest);
36 }
37 }
38 };
39 t.start();
40 result = finalResult;
41 } else {
42 for (int i=0; i < fRepeatTimes; i++) {
43 result.startTest(this);
44 setUp();
45 try {
46 runTest();
47 }
48 catch (AssertionFailedError e) {
49 result.addFailure(this, e);
50 }
51 catch (Throwable e) {
52 result.addError(this, e);
53 } tearDown();
54 result.endTest(this);
55 }
56 }
57 }
58 public int countTestCases() {
59 return fRepeatTimes;
60 }
61 }
--------------------------------------------------------------------------------

唔,这看起来开始变得更坏了。为了支持两个新的特征,我们现在拥有了三个构造子,而且run
(TestResult? result )方法的大小迅速的膨胀起来。
即使不管所有这些新代码,我们这些程序员还没有满足这些需求:我们仍然不能在各自的线程中重复 运行测试。为了这个目的,我们必须添加更多的代码。算了,我就放过你吧。
重构可以帮助这些代码减小尺寸。但是只需要稍做思考:如果再接到一个新的需求,我们要怎么办?
现在JUnit 3.1 支持四种不同的TestCase? 修饰器,它们可以轻松的随意组合以获取所需的功能。同时,JUnit 的实现仍然简单——没有混乱的代码。这种设计保持Test Case 类的简单、轻量级,用户只需要在需要的时
候对TestCase? 对象进行装饰即可,而且可以选择任何组合顺序。
很清楚,这是一个模式帮助简化设计的例子。这个例子也说明了缺乏经验的开发者怎样改善他们的设计——如果他们知道模式指出的重构目标。
使用模式来开发软件是聪明之举,但如果你缺乏使用模式的经验,它也可能是危险的。出于这个原因,
我极力提倡模式学习组。这些学习组让人们在同伴的帮助下稳步前进而精通模式。
当人们了解模式并以受过训练的方式使用它们时,模式是最有用的——这种受过训练的方式就是XP 的方式。以XP 的方式使用模式鼓励开发者保持设计的简单、并完全根据需要对模式进行重构。它鼓励在设 计早期使用关键的模式。它鼓励将问题与能帮助解决问题的模式相匹配。最后,它鼓励开发者编写模式的 简单实现,然后根据需要发展它们。
在XP 的场景中,模式的确更有用;而在包含对模式的使用时,XP 开发则更有可能成功。
参考书目参考书目参考书目参考书目 


[Beck1 00] Beck, Kent. Email on extremeprogramming@egroups.com, January 2000.
[Beck2 94] Patterns Generate Architectures, Kent Beck and Ralph Johnson, ECOOP 94
[GHJV1 95] Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard
Helm, Ralph Johnson and John Vlissides. 中译本:《设计模式:可复用面向对象软件的基础》,李英军等译。
[GHJV2 95] Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard
Helm, Ralph Johnson and John Vlissides. Pages 353-354 中译本:《设计模式:可复用面向对象软件的基础》,
李英军等译,第6 章。

[Jeffries 99] Jeffries, Ron. Patterns And Extreme Programming. Portland Pattern Repository. December,
1999
[Kerth 99] Kerth, Norm. Conversation, circa March, 1999.
[Kerievsky 96] Kerievsky, Joshua. Don’t Distinguish Between Classes And Interfaces. Portland Pattern

Repository. Circa 1996
   发表时间:2004-02-10  
我认为应经常与模式搞好关系, 也就是说, 要熟悉它,  这样我们重构的时候才有可能信手拿来. 从而使重构出的设计更合理.  还有就是我个人觉得现在国内不是把模式用的over engineer了, 因为理解模式并不是一件轻松的事情. 能用上已经不错了. 也许我太悲观了一些吧. 

我觉得认为把模式用的over engineer了, 这是一个相对的问题, 如果大家对模式都很熟, 恰恰相反, 不管从设计, 还是从code, 大家心里都有一个重用的概念. 这等于说是简化了开发. 当然, 后期不同层次的人进行维护又是另一回事情了.

其实很多模式, 我们也常在用, 我觉得是可以在开始设计的时候使用的, 象factroy, abstract factory,  fascade, singleton 等, 还有象J2EE的VO, DAO, Session fascade, view helper等.  这些模式理解起来并不是很困难, 而且它们的适用范围也比较清晰, 完全可以在可以预见的情况下, 在设计中直接使用. 从而减轻项目后期重构的压力.

在重构过程中,  也还是有一些简单的模式可以直接拿过来使用, 象 adapter, proxy, decorator, stratgy, builder. 这几个模式难度适中(我个人感觉).


模式中template, iterator在Java中都有实现. 所以理解起来也不难.

在模式中, 我觉得state, visitor, command理解起来有困难,  到现在也只是有个大概的感觉.  但这三个模式在重构中作用不小, 大家有兴趣可以多讨论讨论.
0 请登录后投票
   发表时间:2004-02-10  
Martin fowler 说的很好:

我对于采用 XP 的人使用 patterns 的建议:

花点时间学习 patterns。

留意使用 patterns 的时机 (但是别太早)。

留意如何先以最简单的方式使用 patterns,然后再慢慢增加复杂度。

如果用了一种 pattern 却觉得没有多大帮助-不用怕,再次把它去掉。

我认为XP应该要更加强调学习 patterns。我不确定它要怎么和 XP 的实务技巧搭配,不过相信 Kent 会想出办法来的。
0 请登录后投票
   发表时间:2004-02-10  
学习设计模式的重点在于理解各个设计模式的使用范围,知道其约束。GOF这本书很好的解释了设计模式是什么,什么时候可以用设计模式,但是它没有告诉我们什么时候就不应该用设计模式。这样的问题不是XP所特有的,而是所有的设计方法都要面对的。当我们面对一个实际的场景的时候,不可能像GOF上那么典型。而这个时候使用和不使用就需要我们去权衡。当然我认为设计就是一个权衡的过程,所以权衡该不该用一个GOF也就是一个应该做的设计决策。
简单就是美。当你不使用GOF一样可以完成工作,并且不是那么困难,且代码也没有因此变得复杂的时候,你又何必去应用一个GOF呢?而实际上问题往往是我们不好权衡使用一个GOF带来的好处和我们将要付出的代价那个更高,这个时候个人的习惯就是起决定性的。
不过XP的方法重视重构,使其所推荐大家使用的策略是可用可不用GOF的时候就不用,即使将来需要应用它们,还又重构的机会。这个时候他们认为总的成本是下降的。
而实际上国内的大多数人还不知道什么时候可以用设计模式,也就是他们还不存在那个权衡的基础。这不是XP的问题,而是那些人的基本素质问题。而其实在这个素质的前提下,问题一样是可以解决,那就是通过重构学习设计模式。在我看来设计模式是一种良好的代码风格,保持一个面向对象的设计思路,使用这样的风格,需要在重构中不断的去强化它们。重构是一种逐步的改进,虽然以设计模式为目标的时候重构会更加有效率,但是没有这个明确的目标,而只是去消除那些味道,也往往会得到设计模式。其实设计模式的来源就是当初GOF从实际中使用面向对象的风格写代码的结果,所以重构这种高度面向对象的过程也可以得到同样的结果,以至于是更多的结果。
所以XP和设计模式,不是矛盾的东西。XP中的重构就是一种学习设计模式的最好途径。
0 请登录后投票
   发表时间:2004-02-10  
ozzzzzz, 听你和dlee的software engineer 的讨论, 再结合我以前做项目的经验,长进不少. 

通过XP中的重构来学习设计模式的确是很好途径,  学习的过程有认知-->理解--> 运用. 我认为  认知-> 部分理解应是从学习GOF的设计模式开始,   进一步的理解->最后的运用应如你所说通过XP中的重构来学习设计模式.  个人意见.
0 请登录后投票
   发表时间:2004-02-10  
我认为学习就是为了使用,如果只是为了学习就只能成为一个研究者,而不是创造者。
设计模式还是要在实际中不断的使用才可以明确其核心的含义的,而通过重构可以知道引入模式的前后的场景区别,从而直接去理解模式的核心思想。而这样在实际中学习的方法,我认为更加有效率。
其实多数情况下,初学者学习设计模式根本就不是很复杂,只要知道一个设计模式的大概意思就可以,重要的是必须记住它们的使用场景,也就是知道什么时候可以用什么模式了,这个时候再去查看那个设计模式的实现细节。
而如果是在重构中设计模式往往会在过程中自动的浮现出来,从而使你自动的记住那些设计模式的实现细节。
这就是从两个方向去学习设计模式,自然效果应该更好。
0 请登录后投票
   发表时间:2004-02-10  
gigix 翻译的这篇文章写的非常好。我学习 XP 的时候也曾经有过这些疑问。其实 XP 与用例、设计模式、UML 等概念并不是矛盾的关系。如果你觉得你这些方面掌握得非常好,你完全可以把这些技能用起来。但是 XP 并不依赖于这些技能,XP 强调用更简单的方法来解决问题(例如,如果文本能描述清楚你的设计意图,就不一定要用 UML 图)。不过你千万不要以为 Kent Beck 不懂设计模式,那就是笑话了,事实上可能他才是最早发现设计模式的人。

我的看法与 o6z 完全相同。我现在考虑更多的是如何得到一个与问题的复杂度相匹配的设计,然后尽量简化这个设计,只要能解决好我目前要解决的问题。由于我非常熟悉 GOF,所以我有信心自己不会得到一个愚蠢的设计以至于将来给自己制造麻烦。如果我发现 GOF 非常有用(就是发现了使用 GOF 的时机),我肯定会使用 GOF 的。但是我是高度关注于解决问题的,我是为了更好地解决这个问题而使用 GOF,而不是为了使用 GOF 而使用 GOF。其实一个有大量 OOP 开发经验的 Java 程序员掌握 GOF 并不是很困难的事情。相反,死读书才是最不容易掌握 GOF 的。

工业界的想法是如何利用一些成熟的技术建造出一个全面的解决方案,帮助客户解决好他们的业务问题,使客户能够最大限度利用信息化的优势,增强核心竞争力,击败竞争对手。
学术界的想法是实验各种新的理论,将成果写出论文(结果是文档而非代码)。越是新的理论越容易出名,例如 AOP、MDA,etc. 这个理论不一定要经过实践检验。这有点象 Brainstorm 会议,重要的是点子的数量而非质量。

现在国内有一种不好的倾向就是明明不是在研究机构里面,非要把自己装扮成一个做研究的。学了设计模式就把设计模式夸大到不合适的地位,似乎一切软件开发中的问题都可以用设计模式来解决。我和 robbin 等朋友的共识是设计模式如同数据结构、操作系统、编译原理、数据库原理、OOP 等等一样只是一个好的程序员必须掌握好的基础,如此而已。但是是否基础的就是不重要的?当然不是这个意思,否则我就没必要多次强调基础的重要性了。
0 请登录后投票
   发表时间:2004-02-11  
是否应用设计模式,显然取决于软件的需求。但是,需求是有变化的,所以在开发软件的时候,一定的前瞻也是必需的。我倒是认为XP和OOAD并不是矛盾的,而是有机的结合。有点类似OO和AOP的组合,当然,XP所占的分量要大于AOP在OO中的分量。

个人认为XP的一个难点在于前期是否进行设计。我认为是需要的,如果没有设计的话,对成员的要求就会很高,否则一个新手写出来的代码,与其重构,恐怕还不如推倒重写。如果没有一定的设计做引导,小组的每一个成员都必须能够做到手中无剑,心中有剑。但是如果有一定的设计作先导,则可以在一定程度上解决这个问题。而程序员的两两配对开发,固然是为了提倡测试,同时,也可以进行经验的交流,在实践中提高成员的水平。所以我觉得XP和OO,并不是两种不同的方法,而是相辅相成的,只是侧重点不同而已。
0 请登录后投票
   发表时间:2004-02-11  
纯粹的XP其实是认为没有必要做什么前瞻性的设计,而且干脆就认为联传统的设计都是多余的。所以很多人攻击XP是在提倡回到fix&code的做法。但是实际上XP反对的是前置设计,提倡的是持续设计、持久设计。
XP所提倡的TDD,是一种把设计中各种虚的权衡的工作具体化的做法。同时由于有了TDD所提供的优秀的测试体系的保证,以及持续集成的支持,使重构成为一种功能强大的设计手段。也只有在这样的环境中重构才能成为银钳子。
而XP对所谓的前瞻性是完全的不认同的,也不支持这样做。他们认为即使是一件事情将来肯定会发生,但是只是不知道何时发生,或者何地发生,就不要对其采取针对性的手段。因为到时候情况往往时发生了变化,很多条件已经改变,原来的预案是不是还是合理的,就很有问题。而这样的策略,在多数情况下是合算的、低成本的。并且由于有重构的技术保证,使修改维护的成本进一步降低到一个最低的水平。
可以说在XP下使用设计模式已经合传统意义上使用的设计模式有所不同。传统下是为了是程序结构更加灵活和适应变化,而XP下则是让程序结构更加稳定和优雅。
0 请登录后投票
   发表时间:2004-02-11  
ozzzzzz 写道
纯粹的XP其实是认为没有必要做什么前瞻性的设计,而且干脆就认为联传统的设计都是多余的。所以很多人攻击XP是在提倡回到fix&code的做法。但是实际上XP反对的是前置设计,提倡的是持续设计、持久设计。
XP所提倡的TDD,是一种把设计中各种虚的权衡的工作具体化的做法。同时由于有了TDD所提供的优秀的测试体系的保证,以及持续集成的支持,使重构成为一种功能强大的设计手段。也只有在这样的环境中重构才能成为银钳子。
而XP对所谓的前瞻性是完全的不认同的,也不支持这样做。他们认为即使是一件事情将来肯定会发生,但是只是不知道何时发生,或者何地发生,就不要对其采取针对性的手段。因为到时候情况往往时发生了变化,很多条件已经改变,原来的预案是不是还是合理的,就很有问题。而这样的策略,在多数情况下是合算的、低成本的。并且由于有重构的技术保证,使修改维护的成本进一步降低到一个最低的水平。
可以说在XP下使用设计模式已经合传统意义上使用的设计模式有所不同。传统下是为了是程序结构更加灵活和适应变化,而XP下则是让程序结构更加稳定和优雅。


我不赞同XP完全否定前瞻性的设计这种观点。仔细的拜读了Martin Fowler的Is Design Dead的文章,觉得他实际上所持的观点,也是设计和重构相辅相成的观点。实际上,对于有经验的程序员来说,不可能再开始写程序之前心中没有一定的设想。我觉得更多的是两种可能:象Kent这种人,设计/设计模式对于他们来说和吃饭呼吸一样,已经是很自然的事情;对于XP的始作俑者来说,不提倡前瞻性的设计也是为了避免过度的设计。而过度的设计,往往出现的次数更多,而不是缺乏设计。

我觉得设计/重构是一种平衡,在这方面,我同意Martin Fowler的观点,我们还没有找到最佳的平衡点,但是我们不能丢掉任何一种方法。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics