服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

java应用中的反模式开发介绍

设计模式(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)来检测输入信息杂乱的问题。虽然它超出了本文讨论的范围,但是它的确在没有任何"典型用户"偏好的情况下,实现了随机的自动化测试。 架构反模式

  这些反模式广泛地出现在应用程序架构中。

  重新发明轮子

  这个术语不需要任何解释。在开发软件的时候,一般有两种选择--在已有的技术上建立或者从草稿开始。尽管在不同的情况下,两者都可能适用,但是在重新发明轮子(为了满足需求,重新开发已经存在的功能或函数)之前分析已有的技术仍然是有用处的。这可以节约时间、金钱,并提升开发者已有的知识水平。

  厂商的锁定

  当软件部分或整个依赖于特定厂商的时候会发生这种情况。j2ee优势之一就是其轻便性,但是它仍然赋予了厂商提供丰富的拥有版权(所有权)特性的机会。的确,在开发过程中,这些特性可能是有帮助的,但是它们有时也有负面作用。其中出现的一个问题是控制权的丢失。你可能非常熟悉和喜欢六个月之前的某种特性。另一个问题是,当厂商作出改变(修改)的时候,可能会打乱你的软件开发、降低协同工作的能力、强迫你持续升级等等。

  解决这个问题的一种方法是在版权元素的上面提供一个隔离层。例如,commons logging就可以插入任何日志记录框架组件(例如log4j或java logging)之中。

  j2ee特定的反模式

  表2列举了普通应用程序层的几个反模式。

  表2:普通的j2ee反模式。这个表列举了j2ee应用程序的不同层次中出现的反模式。

反模式
持久层 捞网
窒息
jsp 对话数据太多嵌入的导航信息
servlet每个servlet中都有公用函数访问优良纹理的ejb接口
ejb大的事务在jms中过多的查询
web服务假设soa = web服务
j2ee硬编码的jndi查找没有有效利用ejb容器

  下面解释表2中列举的反模式。

  持久层反模式

  它出现在那些需要从数据库检索数据的应用程序所引起的一些有趣的挑战中。

  捞网(dredge)

  例如,我们来考虑一下浅度和深度的数据查询。开发者一般喜欢在一个操作中执行昂贵的(深度)查找,载入的信息量超过了需要的信息,而不喜欢通过分部的(浅度)查找来检索必要的数据以供显示和生成报表。这种策略可以在某些情形中使用,但是如果我们没有仔细地计划,它很容易引起性能低下、占用大量内存的问题。

  窒息(stifle)

  j2ee应用程序包含了几个网络资源之间的交互操作--数据库就是其中一种。当应用程序与数据库交互操作的时候,我们就应该调整它,只让它消耗最合适宜的网络带宽,否则就可能导致伸缩性的问题。如果没有优化数据库通讯的网络管道,软件应用可能遇到瓶颈,甚至于会阻塞网络本身。

  jsp和servlet反模式

  jsp和servlet开发的过程中也有一些反模式。

  对话(session)数据过量

  对于开发者来说,把jsp对话作为通用的数据空间是很有诱惑力的。对话为我们存储那些传输中的数据提供了一种简单的机制。但是网站访问量和相应的对话数据的增长可能引起崩溃。同时,粗心地使用对话可能导致键冲突,引发应用程序错误。更糟糕的情况是,可能出现跨越多个对话的共享数据错误(本来不应该共享的),可能导致敏感的用户信息泄漏。

  即使没有这些技术问题,让对话不断"膨胀"也不是好的想法。对话数据应该限制在一定的范围中,只用于存储那些跨越多个用户界面的工作流所必要的数据,当那些信息不再需要的时候,就应该被清除掉。

  嵌入的导航信息

  当开发者硬编码(hardcode)或者向其它的jsp嵌入链接的时候可能会发生这种情况。如果页面的名称改变了,就必须搜索和改变其它页面中的所有相关的链接。我们必须在开始的时候就慎重地考虑导航方案,才能避免在后面的维护阶段出现"梦魇"。

  每个servlet中都有公用函数(功能)

  在应用程序的多个servlet中经常出现硬编码函数(功能)会使应用程序难于维护。作为代替,开发者应该删除servlet中重复的代码。请使用一个框架组件(例如struts)提供简单的、在xml文件中指定工作流程的途径(我们可以更改这个文件而不需要改变servlet代码)。还有其它一些工具,例如前台管理员(front controller)和过滤器也可以在一定的程度上移除硬编码。

  访问优良纹理的(fine-grained)ejb接口

  在通过网络进行远程调用的时候,数据的列集(marshaling)和散列(unmarshalling)操作可能引发应用程序中主要的时间延迟。多重远程调用也可能引发延迟。根据应用程序的不同需求,其解决方案可能包括重新设计ejb,但是如果这不是问题的起因,servlet可能无法通过优良纹理的api来访问ejb实体。如果该ejb同时暴露了优良纹理的和粗糙纹理的api,那么servlet就会用单个的粗糙纹理api调用来代替优良纹理方法的多个调用。 ejb反模式

  这些反模式涉及一些企业级的方面,例如事务和消息队列。

  大型事务

  那些包含了调用多个资源的复杂处理的事务可能会把其它一些等待相同资源的线程锁定一段时间。这对性能可能产生重要的影响。一般情况下,把这些事务分解成较小的片断(依赖于应用程序的需求)、或者使用替代的方法(例如用存储过程来替代长的数据库处理)是一种谨慎的做法。

  jms中队列过载(overloading)

  jms同时提供了队列和主题目的地。队列可以作为不同类型的消息(例如二进制的地图消息或基于文本的消息)的"家"。但是如果你利用这个特性,在同一个队列中发送不同类型的消息,那么区分消息就变成了使用者的责任了。更好的解决方案是分开发送。你可以编程或者使用不同的目的地来实现这样的方案(这依赖于不同的应用程序需求)。

  web服务反模式

  随着web服务的增长,某些特定的反模式将变得很突出。

  认为soa=web服务

  面向架构的服务(soa)这个术语经常与web服务混淆了,但是事实上soa的概念比web服务的概念出现得早。soa是为了实现组件之间的松散耦合。它提供一个软件服务,供另一个软件服务使用。但是后来,soa与web服务的含义变得相同了。我们要记得把soa作为其它服务技术的基础是完全可行的。

  其它的反模式

  还有其它一些反模式不好分类,但是仍然值得我们注意。

  硬编码的jndi查找

  java命名和目录接口为我们提供了一条查询对象信息(例如ejb接口的位置)的便捷途径。但是jndi查找操作一般是很昂贵的,特别是在重复执行的情况下。但是如果你缓冲了其结果,就可以获取显著的性能改善。其中一种实现方案就是使用单态(singleton)类。

  但是网络不是静态的。随着网络的改变,查询也可能改变。这意味着对于网络的每种变化,程序员必须重新访问原应用程序代码,做出必要的修改,并重新编译/部署代码。其替代办法是,通过xml配置文件来提供可配置的查找,这样就可以使开发者在每次遇到网络发生改变的时候,不必重新编译代码。

  没有充分利用ejb容器的特性

  在我们开放企业级组件的时候,可以从两种办法中选择--使用容器提供的服务或者编写自己的服务。尽管ejb遇到了大量的替代产品(例如spring),但是它仍然被广泛地被用于与这些框架组件协同工作。在我们使用ejb的时候,你应该试图利用容器的服务,例如群集、负载均衡、安全性、事务管理、容错和数据存储。如果你没有充分地利用容器的丰富特性,最终可能导致"重新发明轮子"(这是本文前面提到的另一种反模式)。

  我相信你已经认识到反模式与设计模式的重要性相当。即使你还没有明白本文描述的某些反模式的名称,你也应该能够记住它们的特性和可能引起的问题。对这些反模式进行分类和命名所带来的好处与设计模式的分类和命名是一样的;这样做可以为软件经理、架构师、设计者和程序员提供一个通用的"词典",帮助他们认识未来的错误和维护麻烦可能的根源。

扫描关注微信公众号