但这是避免使用模式的一个原因吗?
我想,不。我发现模式在如此多的项目中如此有用,以至于我无法想象不使用它们来进行软件设计和开发。我相信对模式的彻底的学习是非常值得的。
那么,XP对模式保持沉默是因为感觉到它们将被误用吗?
如果情况是这样,也许问题已经变成:我们怎样使用模式中的智慧,而避免模式在XP开发场景中的误用呢?
在这里,我想我必须回到《设计模式》。在“结论”一章、“设计模式将带来什么”一节、“重构的目标”小节中,作者写道:
我们的设计模式记录了许多重构产生的设计结构。在设计初期使用这些模式可以防止以后的重构。不过即使是在系统建成之后才了解如何使用这些模式,它们仍可以教你如何修改你的系统。设计模式为你的重构提供了目标。[GHJV2 95]
这就是我们需要的观点:重构的目标。这就是重构和模式之间的桥梁。它完美的描述了我自己在如何使用模式方面的进步:从简单开始,考虑模式但将它们保持在次要地位,小规模重构,只有在真正需要模式的时候才把重构转移为模式。
这个需要训练和仔细判断的过程将很好的适应XP所包含的最好的习惯。
而且这个途径很明显与“故意不知道或不使用模式而只依赖重构来改善设计”的方法非常不同。
只依赖重构的危险是:没有目标,人们可能使设计小小进步,但他们的全面设计将最终受损害,因为这种方法缺乏顺序、简单性和效力,而聪明的使用模式则可以让开发者拥有这些。
引用Kent Beck自己的话:模式生成体系结构。[Beck2 94]
但模式不保证有纪律的使用。如果我们在设计中过多、过早的使用它们,我们就又回到了过分设计的问题。因此,我们必须回答这个问题:“在设计的生命周期中,何时引入模式是安全的?”请回忆上面对《设计模式》的引用:
在设计初期使用这些模式可以防止以后的重构。
这是一个聪明的主张。如果我们不知道“何时配置一个模式”的基本规则,那么我们就很容易在设计周期的早期就陷入过分设计。
再一次,问题又全部集中在一起:如何将项目中的问题与一个合适的模式相匹配。
在这里,我必须讲述我为不同行业开发软件得到的经验。
有一家客户要求我和我的团队用JAVA为他们的网站构造软件,这将是一个很酷的交互式版本。这个客户没有任何JAVA程序员,但仍然要求能在他们需要的任何时候、任何地方修改软件的行为,而不必做程序的修改。多么高的要求!
在对他们的需要做了一些分析之后,我们发现Command模式将在这个设计中扮演一个非常重要的角色。我们将编写命令对象,并让这些命令对象控制软件的整个行为。用户将可以参数化这些命令、将它们排序、并选择命令运行的时间和地点。
这个解决方案工作得很完美,Command模式正是成功的关键。所以在这里,我们不会等到重构的时候才使用Command模式。相反,我们预先看到了使用它的需要,并从一开始就用Command模式来设计软件。
在另一个项目中,系统需要作为独立应用程序和WEB应用程序运行。Builder模式在这个系统中发挥了巨大的作用。如果没有它,我不敢想象我们会拼凑出一个多么臃肿的设计。Builder模式的作用就是解决“多平台、多环境运行”这样的问题。所以在设计早期就选择它是正确的。
现在,我必须声明:即使在设计的早期引入了模式,但一开始仍然应该按照它们最原始的样子来实现它们。只有在晚些时候,当需要附加的功能时,模式的实现才能被替换或升级。
一个例子会让你更清楚这一点。
上面提到的由命令对象控制的软件是用多线程的代码实现的。有时候两个线程会使用同一个宏命令来运行一系列命令。但一开始我们并没有被宏命令的线程安全问题困扰。所以,当我们开始遇到线程安全造成的莫名其妙的问题时,我们必须重新考虑我们的实现。问题是,我们应该花时间构造宏命令的线程安全吗?或者有没有更简单的方法来解决这个问题?
我们用更简单的方法解决了这个问题,并且避免了过分设计:为每个线程提供一个独立的宏命令实例。我们可以在30秒内实现这个解决方案。请把这个时间与设计一个线程安全的宏命令所需的时间做一下比较。
这个例子描述了XP的哲学怎样在使用模式的情况下保持事情简单。没有这种简单化的驱动,过分设计的解决方案――就象线程安全的宏命令――很容易出现。
因此,简单化和模式之间的关联是很重要的。
当程序员需要做出设计决策时,很重要的一件事就是:他们应该试图保持设计简单,因为简单的设计通常比庞大而复杂的设计更容易维护和扩展。我们已经知道,重构意味着将我们保持在简单的路上:它鼓励我们以小而简单步骤逐渐改进我们的设计,并避免过分设计。
但是模式呢?难道它们不是帮助我们保持简单吗?
有些人会说“不”。他们认为模式尽管有用,但容易造成复杂的设计。他们认为模式会造成对象快速增加,并导致过分依赖对象组合。
这种观点是由于对使用模式的方法的错误理解。有经验的模式使用者会避免复杂的设计、对象的快速增长和过多的对象组合。
实际上,在使用模式的时候,有经验的模式使用者会让他们的设计更简单。我将再用一个例子来说明我的观点。
JUnit是一个简单而有用的JAVA测试框架,它的作者是Kent Beck和Erich Gamma。这是一个精彩的软件,其中满是精心选择的简单的模式。
最近一些人要求我对JUnit进行DeGoF,也就是说,将JUnit中的设计模式移除掉,以观察没有模式的JUnit是什么样子。这是一次非常有趣的练习,因为它让参与者认真考虑应该在什么时候在系统中引入模式。
为了描述他们学到的东西,我们将对JUnit 2.1版中的一些扩展进行DeGoF。
JUnit中有一个叫做TestCase的抽象类,所有的具体测试类都派生自它。TestCase类没有提供任何多次运行的方法,也没有提供在自己的线程中运行测试的方法。Erich和Kent用Decorator模式很优雅的实现了可重复测试和基于线程的测试。但是如果设计团队不知道Decorator模式呢?让我们看看他们会开发出什么,并评估一下他们的设计有多简单。
这是Test Case在JUnit框架1.0版本中的样子(为了简化,我们忽略了注释和很多方法):
public abstract class TestCase implements Test {
private String fName;
public TestCase(String name) {
fName= name;
}
public void run(TestResult result) {
result.startTest(this);
setUp();
try {
runTest();
}
catch (AssertionFailedError e) {
result.addFailure(this, e);
}
catch (Throwable e) {
result.addError(this, e);
}
tearDown();
result.endTest(this);
}
public TestResult run() {
TestResult result= defaultResult();
run(result);
return result;
}
protected void runTest() throws Throwable {
Method runMethod= null;
try {
runMethod= getClass().getMethod(fName, new Class[0]);
} catch (NoSuchMethodException e) {
e.fillInStackTrace();
throw e;
}
try {
runMethod.invoke(this, new Class[0]);
}
catch (InvocationTargetException e) {
e.fillInStackTrace();
throw e.getTargetException();
}
catch (IllegalAccessException e) {
e.fillInStackTrace();
throw e;
}
}
public int countTestCases() {
return 1;
}
}