设计模式(design pattern)为我们提供了一条途径,使我们能够进行简洁明了地沟通,以得到期望的软件经验;反模式与此类似,只不过它是用于沟通那些不合乎需求的经验的--下面是一些可以帮助你起步的通常碰到的反模式。
反模式是一种典型的、糟糕的设计;换句话说,它是设计模式的对立面--设计模式提出的是良好的设计。在某种意义上讲,反模式表现的是糟糕的解决方案,它让相关人员更轻易理解根本的问题和问题之间的因果关系。尽管了解设计模式很重要,但是我相信理解反模式也是一样重要的--我们应该理解反模式。
我们来证实一下自己的观点。软件世界是围绕应用程序的维护来"运行"的。当然,每个软件产品的生命周期都是从构造的时候开始的,但是在开始大量生产以后,就需要维护了。根据开发小组的技巧,产品可能拥有"良好的"或"糟糕的"设计,其中的"良好"或"糟糕"是对应于一定的环境中,因为一种良好的设计假如应用在错误的环境中也可能成为一种反模式。例如,在单应用程序(single-application)服务器环境中使用Singleton是恰当的,但是在群集应用程序(clustered-application)服务器环境中,假如它没有被正确地处理好,就会产生很多问题。与正面的设计模式形成对比的是,反模式引出的是负面的解决方案或以前的方法(去年的解决方案在今年就可能是反模式了),这可能是由于小组成员缺乏足够的信息,或者在实现设计或解决问题方面作出了糟糕的判定。
在产品开始生产并进入维护模式之后,真正的问题才开始显现。一个从未参与产品开发的从事维护工作的开发者可能给项目引入"糟糕的设计"元素。但是假如这种糟糕的实践被编写为反模式"法典",就可以预先提醒开发者,避免最普通的缺陷,就像设计模式"法典"为开发者提供了识别出普通的有用的技术途径一样。根据这种逻辑,反模式是一种值得我们记载的普遍存在的糟糕的设计。
当我们使用J2EE等技术的时候,这种方法的优势尤其明显。J2EE的初始设计哲学强调简单性,但是它的复杂程度已经变得难以置信了。在这种复杂的环境中,模式和反模式同时为软件经理、架构师、设计师和开发者提供了通用的"词典"。
无论在构造模式还是在维护模式中,为了获得成功,我们理解反模式都是必要的。在反模式被记录下来之后,开发者一般可以熟悉到这些负面的模式,以根除糟糕的设计,改善软件。
本文从软件架构和开发的角度来谈论反模式。接着它提出了在J2EE应用程序的大多数通用层次(用户界面、永续性、EJB等)中普遍存在的反模式。它的全部目标是为这些反模式提供背景知识,并为避免这些问题提供建议。
表1列举了本文中讨论的三种普遍的设计、开发和架构的反模式。
表1:普遍的反模式
领域
普通的反模式
设计
编写具体的类而不是接口在代码中耦合了逻辑(例如日志记录、安全性和缓冲)
开发
Golden Hammer(金锤)Input Kludge(输入杂乱)
架构
Reinvent the wheel(重新发明轮子)Vendor lock-in(厂商的锁定)
普遍的反模式
上表列举的反模式跨越了广阔的开发者领域。
编写具体的类而不是接口
这是一条重要的设计原则,但是却经常被破坏--编写接口而不是具体的类可以提供数不清的优点!你不会被"捆绑"在使用某种特定的实现上,同时可以在运行时改变行为。"接口"这个术语意味着要么是一个Java接口,要么是一个抽象类。只要你应用多态性(polymorphism),应用程序的行为就不会被"锁定"在特定的代码中。请注重,当你知道其行为不会改变的时候,这条规则就不适用了。
编写一个实现的例子如下所示:
Dog animal = new Dog();
animal.bark();
作为对比,编写一个接口的例子是:
Animal animal = getAnimal(Dog.class);
animal.makeNoise();
上面的两个例子有两个不同点。第二个版本使用制造厂(factory)方法来动态地获取Dog类的实例。同时,第二个版本泛化(generalize)了bark()方法,它是makeNoise()方法的Dog具体实现,而任何动物都可以实现这个方法。下面的代码显示了AnimalFactory类的getAnimal()方法和Dog类,它举例说明了一个特定的Animal实现。
getAnimal(Class c)
{
if (c == Dog.class)
return new Dog();
// 此处测试其它类型
}
class Dog
{
makeNoise()
{
bark();
}
}
过多的耦合
我们在编写代码的时候,需要紧记一条基本的软件观念--一般情况下,耦合越少越好。例如,你编写一段代码来执行某项事务,那么它就应该只执行该项事务,这样代码才整洁、轻易阅读、易于维护。但是有些东西是不可避免的,例如日志记录、安全性和缓冲等方面可能需要耦合。幸运的是,有些办法可以避免这种情况的发生。其中的一种技术--面向方面的编程(AOP)通过在编译时(compile time)给应用程序注入方面(ASPect)的行为,为达到这个目的提供了一条灵巧的途径。
开发:金锤反模式
过多地或者强制性地使用某种技术或模式可能会导致金锤反模式。精通特定技术或软件的团体或个人倾向于在特性相似的其它项目中也使用类似的技术--即使其它的技术更适合那种情况。他们把不熟悉当作是冒险。此外,计划和评估熟悉的技术也更加简单。通过书本、培训和用户组(例如Java用户组)的形式来扩充开发者的知识对避免这种反模式非常有益。
输入信息杂乱
软件错误地处理简单的用户输入信息就会形成输入信息杂乱。例如,Web站点让用户输入ID和密码来登录,那么它接受的输入内容就应该仅仅是可用作ID和密码的字符。假如该站点的逻辑拒绝了无效的输入,那么它在这个方面就是安全的,但是假如它没有拒绝这些无效的输入,就可能出现不可预料的结果。输入信息杂乱很轻易被最终用户发现,但是在开发者进行单元测试的时候很难发现。
你可以使用怪用测试(monkey test)来检测输入信息杂乱的问题。虽然它超出了本文讨论的范围,但是它的确在没有任何"典型用户"偏好的情况下,实现了随机的自动化测试。