目录
摘要 ……………………………………………………………………2
文献综述 ………………………………………………………………3
第一章 前言……………………………………………………………6
第二章 OOP的基本原则及发展方向
第一节 软件的可维护性与可复用性…………………………6
第二节 六条OOP设计原则 ……………………………………7
第三节 AOP的兴起…………………………………………… 8
第三章 J2EE系统的架构设计
第一节J2EE中常用的概念……………………………………10
第二节 MVC架构 ………………………………………………12
第三节 分布式架构……………………………………………13
第四章 数据持久层的设计
第一节 业务对象的持久化……………………………………14
第二节 数据访问对象设计模式………………………………15
第三节 ORM框架的原理和重要性 ……………………………16
第四节 数据持久层……………………………………………19
第五节 数据库连接池、缓存及系统性能的提升 ……………21
第六节 Java反射技术 ………………………………………22
第五章 J2EE架构中各层的数据表示方法
第一节 MVC三层体系结构中的数据表示要求 ………………23
第二节 J2EE系统中各层数据表示的设计 …………………24
第六章 设计模式的概念与几种常用的J2EE设计模式
第一节 设计模式的概念………………………………………25
第二节 工厂创建模式与单例模式……………………………27
第三节 使用工厂模式实现DAO ………………………………31
总结 ……………………………………………………………………33
结束语 …………………………………………………………………34
多层J2EE系统的架构与模式设计
【摘要】 J2EE提供了一套完整的基于标准化模块的服务组件,它能自动的处理大多数应用程序的细节,而不需要复杂的编程,因此简化了复杂的企业级应用程序的开发。本文首先考察企业级应用的一般概念和需求,然后简要阐述面向对象程序设计的基本原则,并结合软件工程的思想来讨论多层的J2EE应用架构,分析它们满足企业级应用的方式,,再通过讲述常用的几种Java设计模式和Java反射技术来说明如何实现这些应用架构。
【关键词】 模型-视图-控制,对象关系映射,业务对象,面向方面编程,数据访问对象,设计模式
The Framework of Multitier J2EE System and Design Pattern
【abstracts】The J2EE simplifies enterprise applications by basing them on standardized, modular components, by providing a complete set of services to those components, and by handling many details of application behavior automatically,without complex programming. This paper reviews the general concept and the requirement of enterprise application, elaborates the general principle of object oriented programming briefly. We combine the idea of Software-Engineering to discuss the framework of multitier J2EE, and meanwhile analyze how they can satisfy the demand of enterprise applications. At last, this paper shows how to implement those frameworks of multitier J2EE by introducing some kinds of Java design pattern and the Java reflection technology.
【key words】MVC,ORM, BO, AOP, DAO,Design pattern.
【文献综述】
计算机软件是人类心灵和智慧在虚拟空间中的投射。软件的性能是人类能力的扩展,它的活动就是人类心智活动的反映。软件直接表达出设计者对目标的理解,对用户的期待,以及对自己的定位。人们在自己的环境中不断发现问题和寻找问题的解决方案的时候,发现有一些问题及其解决方案不断变换面孔重复出现,但在这些不同的面孔后面有着共同的本质,这些共同的本质就是模式。著名建筑工程学家Christopher Alexander所著《建筑的永恒之道》( The Timeless Way of Building)和他发展出来的模式理论涵盖科学,心理,艺术和哲学,不仅适用于建筑工程学,而且适用于软件工程学以及任何其他的工程学。
今天的企业软件可以由多个不同的部分组成,但企业已经认识到,只要符合企业利益,很有必要将各个分散的系统进行良好的集成,以尽可能相互支持,总的来说企业希望对集成后的企业级软件的具体应用如下:
1. 通过集成企业的客户支持和本身的产品知识,企业可以利用WEB为它的客户提供更新更好的服务。
2. 将企业售货机联网,企业可以获得更多的在线客户。
3. 将销售管理系统和存货系统相链接,企业可以设计特定的低成本的Web销售渠道,这样可以进入未曾涉足的市场领域。
4. 如果给企业员工所使用的服务提供一个前端,例如内部办公用品订货系统,将它与会计系统连接在一起,企业就可以降低总体开支并提高员工的工作效率。
5. 在线使用企业HR系统,可以让员工根据他们自己的健康状况进行更多的选择,这样可以降低企业整体的管理费用。
6. 使企业的人力资源密集型操作自动化,并使它可用于任何时间任何地点,在降低整体运营费用的同时,企业还可以给它的客户提供更好的服务。
按企业对企业级软件的要求,一个企业级应用系统(J2EE)肯定会是一个服务于商业目的,处理企业业务信息,数据的软件系统,因此大概可以总结出以下五方面的特征:有复杂的业务逻辑,有大量持久化数据,与多种外部系统相关联有较高的性能要求,在运行时需要随时监控,管理,应该能够实时记录,观察系统运行情况。修改系统配置。
以前的企业应用,集中式的单层(single tier)应用程序占有主导地位。在软件中,层是一个抽象概念,它的主要目的是通过将软件分解成独立的逻辑层,帮助我们理解与特定应用程序相关联的体系结构。从应用程序的角度看,单层应用程序的最大问题在于,它将表示,业务逻辑和数据都混合在一起。客户机-服务器方法通过将表示和一些业务逻辑分别移至单独的层中,缓解了上述主要问题的影响,不过从应用程序的角度来看,业务逻辑和表示依然很混乱。N层(n-tier)方法可以取得更好的整体平衡,它将表示逻辑与业务逻辑从底层数据中分离开来,以满足特定的需求。单单采用面向对象开发技术后只可以实现部分代码重用,原因之一是对象都细粒度化,正是因为细粒度对象间更紧密的耦合状态,从而便利大范围的重用变得很困难。分层化的组件设计就是为了解决这个问题。与对象不同,软件组件是在更高的抽象级中设计的,可以提供一个完整的功能或服务。组件间的耦合更为松散。利用组件的接口,可以将组件迅速组合在一起以构建更大的企业级应用程序。
近年来,人们已开发出了各种不同的帮助理解的组件模型,例如,Microsoft的ActiveX,后来的COM编程接口,和现在兴起的.net FrameWork,SUN Microsystems的applet和JavaBeans,Enterprise JavaBeans(EJB),其中EJB是J2EE的一部分。
Sun Microsystems把Java2平台组织成三个特定的,引人瞩目的版本:微型版(J2ME),标准版(J2SE)和企业版(J2EE)。在这些产品中,J2EE与开发企业级Java应用联系最紧密。J2EE为开发复杂的,分布式企业级Java应用定义了一套体系结构。
J2EE最初是由Sun Microsystems在1999年中期发布的,其正式发布则在1999年后期。J2EE仍然较新,其依次发布的版本间仍然存在着重大的改变,特别是在EJB方面。该平台是建立在Java“一次编写,随意运行”的理念上的,它通过一组技术和一套API实现。
N层体系结构的概念已经出现一段较长的时间了,并已成功地应用于构建企业级应用程序。Sun在Java中采用n层开发模型,并引入特定功能,允许更容易地开发服务器端可伸缩的、基于Web的企业级应用程序,从而在这个领域提供了Java自身所缺少的关键成分。
为什么要使用J2EE呢?它不是太新并且功能未经证实,它能提供什么?难道只是一种一时的技术狂热吗?在J2EE出现之前,JDBC API早已建立好了,可选用的轻量级的,可维护的servlet技术也已出现。除了这些,J2EE还提供了一些有前景的优点,它让开发人员关注开发业务逻辑,不用预先详细了解执行环境而把精力放到实现系统上,以及创建在硬件平台和操作系统(OS)间更容易衔接的系统。企业级软件开发是一项复杂的任务,需要具备许多不同领域的广泛知识。例如,一项典型的企业级应用程序开发工作可能要求你熟悉进程间的通信问题、安全问题、数据库特定访问查询等。
J2EE企业级开发平台鼓励在系统开发、部署和执行之间作一个清晰的划分。此开发人员可以将部署细节留给部署人员处理,如实际的数据库名称和存放位置、主机持有配置属性等。J2EE让系统可通过Java和J2EE而不是底层系统API被访问,从而支持硬件和OS无关性。由于这种原因,遵循J2EE体系结构技术规范的企业级系统可以非常容易地在硬件系统和不同的OS之间衔接。
在企业级开发领域,虽然面对Microsoft .net强大的挑战,但是J2EE由于上述优点,并且相对说来比较成熟,已经占据了企业级开发的大部分市场,并随着技术的进步、新的J2EE版本的发布、开源社区庞大自由开发者的支持,将会使企业级开发变得更高效,更快速,更高质量,更易于维护。
第一章 前言
J2EE核心技术有十三种,它们和J2EE API覆盖了企业级Java开发的广泛领域。在企业级Java开发工作中要用到的J2EE的方方面面知识是不太可能的。比较常用的有容器,servlet, JSP, EJB等。容器是一种运行在服务器上的软件实体,用于管理特定类型的组件。它为开发J2EE组件提供了执行环境。通过这些容器,J2EE体系结构就能在开发和部署间提供无关性,并在不同类型的中间层服务器间提供可移植性。servlet是一些可生成动态内容的Web组件。它们是当今在www上看到的最常用的J2EE组件之一。它们提供了一种有效的机制,用于基于服务器的业务逻辑和基于Web的客户端之间的交互,还可为通用的CGI脚本方法提供一种轻型且更易于管理的替代方法。JSP是另一种类型的J2EE Web组件,它是从servlet技术发展而来的。事实上,一部分JSP编译进servlet并在servlet容器中执行。EJB技术规范是J2EE平台的最核心的部分。它为构建可伸缩、分布式、基于服务器的企业级Java应用组件提供了一种综合性的组件模型。文章将结合这几种主要的组件技术来讲述构建J2EE系统的一般过程。
第二章 OOP的基本原则及发展方向
第一节 软件的可维护性与可复用性
通常认为,一个易于维护的系统,就是复用率较高的系统;而一个复用较好的系统,就是一个易于维护的系统。也就是说一个系统的设计目标应该具有如下性质:可扩展性,灵活性,可插入性。
常听人说一个项目开发结束只完了这个项目的三分之一,可见系统的可维护的重要性。导致一个系统可维护性降低主要有四个原因:过于僵硬,过于脆弱,复用率低,黏度过高。通过良好的软件复用,可以提高软件的生产效率,控制生产成本,并能提高软件的质量,改善系统的可维护性,提高系统的灵活性和可插入性。
在面向对象的设计里,可维护性复用是以设计原则和设计模式为基础的,下一节介绍面向对象设计的基本原则。
第二节 六条OOP设计原则
OOP设计原则是提高软件系统的可维护性和可复用性的指导性原则,Java是一门纯面向对象的设计语言,因此我们在使用Java开发J2EE系统时必须遵守OOP设计的基本原则。
这些设计原则首先都是复用的原则,遵循这些设计原则可以有效地提高系统的复用性,同时提高系统的可维护性:
l OCP开闭原则:一个软件实体应当对扩展开放,对修改关闭
l LSP(里氏代换原则):它是继承复用的基石
l DIP(依赖倒转原则):要依赖于抽象,不要依赖于具体
l ISP(接口隔离原则):一个类对另一个类的依赖性应当是建立在最小接口上
l CARP(合成/聚合复用原则):要尽量使用合成/聚合,尽量不要使用继承
l LOD(迪米特法则):一个对象应该对其他对象有尽可能少的了解
通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新需求,使变化中的软件系统有一定的适应性和灵活性。而已有的软件模块,特别是最重要的抽象层模块不能再修改,这就使变化莫测中的软件系统有一定的稳定性和延续性。具有这些优点的软件系统是一个在高层次上实现了复用的系统,也是一个易于维护的系统。
里氏代换要求凡是基类型使用的地方,子类型一定适用,因此子类必须具备基类型的全部接口。
传统的过程性系统的设计办法倾向于使高层次的模块依赖于低层次的模块;抽象层次依赖于具体层次。抽象层次包含的是应用系统的商务逻辑和宏观的,对整个系统来说是重要的战略性决定,是必然性的体现;而具体层次则含有一些次要的与实现有关的算法和逻辑,以及战术性的决定,带有相当大的偶然性选择。具体层次的代码是会经常有变动的,不能避免出现错误。抽象层次依赖于具体层次,使许多具体层次的细节的算法变化立即影响到抽象层次的宏观商务逻辑,导致微观决定宏观,战术决定战略,偶然决定必然。从哲学意义上面讲这是很荒唐的事情,倒转原则就是要把这个错误的依赖关系倒转过来。
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,以可通过第三者转发这个调用。
“针对接口编程”现在已经逐渐成为广大OOP程序员的共识,成为OOP设计思想的集中体现。一门语言即使不提供Interface这样的关键字,它也需要在很大程序上模拟出接口的功能。典型的如C++,通过声明一个只有纯虚函数的子类来模拟接口功能。不过这种模拟也就仅限于此,比如对于Java中的动态代理,抽象基类似乎就无能为力了。
OOP经过二十多年的发展,逐渐取代面向过程程序设计,已经相当成熟。OOP的设计思想主要体现在下以几个方面:
(1) 针对抽象编程,不针对具体编程。这是依赖倒转原则所要求的。换言之,应当针对抽象类编程,不要针对具体子类编程,这一原则点出了抽象类对代码利用的一个最重要的作用。
(2) 使用继承达到软件复用的目的。在Java中,继承有两种,一种是接口继承,一种是实现继承。第二种继承常常很容易被滥用。只要可能,尽量使用合成,而不要使用继承来达到复用的目的。
(3) 使用模板方式模式,它是类的行为模式,准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。
第三节 AOP的兴起
软件工程的发展史实际上就是抽象的发展史。随着软件越来越复杂,相应地我们也提高了编程语言的方法的抽象级别。因此,我们经历了从C到C++到Java,从结构化方法到面向对象的设计,从类到设计模型又到体系结构框架这一系列的改变,并且这个改变仍然在继续着。
AOP(Aspect Oriented Programming) 面向方面编程是一种超越OOP的编程模型,它允许程序员将横切关注点(散布在多个模块中的一致概念如同步处理,持久化,日志等都是典型的横切关注点)封装成清晰的可重用模块,然后通过组合这些模块和功能性组件获得系统的实现。AOP通过促进另一种模块性补充了OOP,便利我们可以很自然地处理一些传统的OOP不能解决的问题。
在J2EE应用开发中,我们主要使用AOP的拦截(interception)能力,它提供了在任何对象的方法调用前/后加入自定义行为的能力。这使得我们可以处理企业应用中的横切关注点(同时作用于多个对象的关注点),并且仍然保持强类型,而不需要改变方法签名。例如,可以在一个应该具有事务的方法调用前开始一个事务,在方法返回时提交或者回滚。使用AOP可以把与事务管理相关的重复劳动放进一个框架内。
作为一个开发者,我每天大部分的时间面对的是数据库的存取,JNDI资源的访问,事情的声明释放,以及各处文件的读取等等,这些面对不同业务的同样操作。但是,在执行一步操作的时候往往我们需要配置一个比较完整的环境,如果JSP/Servlet容器,EJB容器等。在当今J2EE主流以发展轻量级构件的时代,我们有机会从摆脱这种重复的劳动,使用上轻量级的构件技术。
轻量级容器依靠反转控制(Inversion of Control. IoC)模式或依赖注入(Dependency Injection)现在是Java社区非常热门的话题,它实际上是针对接口编程这个OOP概念的进一步深化。利用IoC模式能够很好的解决代码调用者和被调用者之间的依赖关系,不仅可以灵活的装配我们的业务对象,更重要的是它让我们有机会在一个完整的环境中进行工作,使我们的业务开发不用再考虑环境因素。如果用得着某几项J2EE服务,就可以使用他们,比如,想用JTA就用JTA,想用数据库连接池就用连接池。这样,让我们的应用代码在J2EE环境下,不受任何运行环境的束缚。更有特色的是,轻量级容器借助AOP的横切服务甚至可以让我们的代码段中不包含try/catch这样的常用代码成例。因为这样这种异常处理是与环境相关的。
从概念上面来说,JNDI查找,事务,安全之类的基础设施都是与业务逻辑横切的。由于业务组件都是轻量级容器负责管理生命周期,使用者只要是通过容器进行组件访问,我们就应该让容器插入额外的代码来管理横切基础设施。然而,将这些基础设计进行外部声明,而不让他们进入到应用代码之中,才是一个系统实现可插入性的最好的实现办法。
在J2EE轻量级容器中,Spring无疑是最流行,发展得最好的,它提供的IoC容器不仅可以帮助我们完成我们的目标,而且它是开源的。
那么Spring如果将我们从重复的劳动中解放出来呢?一般,在我们写数据库接口时,需要访问数据库的时候都需要创建一个Connection,再声明一个statement,然后再进行具体的数据库操作,事务处理完闭之后,还要用try/catch语句将statement,connection close()掉。在JDBC2.0规范中,我们通常所做的方法是通过容器过(Tomcat5.0,Jboss,Weblogic都提供很好的JNDI支持)定义资源,并暴露为全局性的JNDI对象,这样,客户端的代码将直接通过JNDI访问这些资源,显然,应用代码将与JNDI基础设施绑定在了一起,也就是说应用代码与环境相关了,很难借助伪造的资源对象来进行单元测试,而且这些的代码既不易重用,也不能在J2EE环境以外运行。
第三章 J2EE系统的架构设计
第一节 J2EE中常用的概念
在软件业迈向组件装配工业(software component industry) 的过程中﹐不断发现组件设计者对其组件应用场合的预想环境与应用软件师的软件体系结构常常无法很好地整合起来﹐导致应用软件开发人员难以灵活地复用他人设计好的组件,造成软件组件工业发展上的瓶颈。OOP软件专家也逐渐认识到其问题是来自于软件主架构上的不兼容。软件主架构的重要性并非今天才呈现出来﹐20多年前软件大师Fred.P.Brooks 就提到:软件开发者之间﹐他们设计的理念必须一致才能共同创造出简单易用的软件,同时他也强调软件主架构在达到概念一致的过程中,合作居于核心角色。现在,开发者们在项目开始时不是讨论要不要使用架构,而是讨论究竟该使用什么样的架构。
(1) 体系结构(Architecture)
体系结构也可称为架构,所谓软件架构﹐根据Perry 和Wolfe之定义:Software Architecture = {Elements,Forms, Rationale / Constraint },也就是软件主架构 = {组件元素,元素互助合作之模式,基础要求与限制}。Philippe Kruchten采用上面的定义﹐并说明主架构之设计就是:将各组件元素以某些理想的合作模式组织起来﹐以达成系统的基本功能和限制。
(2) 架构(Framework)
框架也可称为应用架构,框架的一般定义就是:在特定领域基于体系结构的可重用的设计。也可以认为框架是体系结构在特定领域下的应用。框架比较出名的例子就是MVC(模型-视图-控制)。
(3) 库(Library)
库应该是可重用的、相互协作的资源的集合,供开发人员进行重复调用。它与框架的主要区别在于运行时与程序的调用关系。库是被程序调用,而框架则调用程序。常见的库有Java API,Apache组织提供的Java开发包。
(4) 设计模式(Design Pattern)
设计模式应该很熟悉,尤其四人帮所写的书更是家喻户晓。“四人帮”将模式描述为“在一定的环境中解决某一问题的方案”。这三个事物 — 问题、解决方案和环境 — 是模式的基本要素。
(5) 平台(Platform)
由多种系统构成,其中也可以包含硬件部分。
在J2EE系统开发过程中,大致可以分为五大步骤:需求、分析、设计、编码、测试。而体系结构是软件的骨架,是最重要的基础。体系结构是涉及到每一步骤中。一般在获取需要的同时,就应该开始分析软件的体系结构。体系结构现在一般是各个大的功能模块组合成,然后描述各个部分的关系,J2EE平台已经为我们提供了整个软件系统的体系结构。
架构是体系结构中每个模块中相对细小的结构。如需要表示Web技术,就会用到MVC架构,而Web功能只是整个软件体系中的一个功能模块。每个架构可以有许多个实例,如用Java实现的MVC架构Struts。
而在架构之下就是设计模式,设计模式一般是应用于架构之中,也可以说是对架构的补充。架构只是提供了一个环境,需要我们填入东西。无论是否应用了设计模式,都可以实现软件的功能,而正确应用设计模式,是对前人软件设计思想或实现方法的一种继承。
第二节 MVC架构
上一节中提到基于Web开发的MVC架构目前在J2EE的世界内空前繁荣。在这些架构中,老牌的有Struts、Web work。新兴的有Spring MVC、Tapestry、JSF等。这些大多是著名团队的作品,都提供了较好的层次分隔能力,在实现良好的MVC 分隔的基础上,通过提供一些现成的辅助类库,促进了生产效率的提高。
在这么多J2EE架构中如何选择一个适合自己项目的架构呢?什么是衡量一个架构设计是否优秀的标准?从实际Web产品研发的角度而言(而非纯粹设计上,扩展性上,以及支持特性上的比较),目前Struts 也许是第一选择。它拥有成熟的设计,同时,也拥有最丰富的信息资源和开发群体。从较偏向设计的角度出发,WebWork2 的设计理念更加先进,其代码与Servlet API 相分离,这使得单元测试更加便利,同时系统从B/S结构转向C/S接口也较为简单。另外,对基于模板的表现层技术(Velocity、Free Maker和XSLT)的支持,也为程序员提供了除JSP之外的更多的选择(Struts也支持基于模板的表现层技术,只是实际中不太常用)。而对于Spring而言,首先,它提供了一个相当灵活和可扩展的MVC实现,与WebWork2相比,它在依赖注入方面、AOP 等方面更加优秀,但在MVC 框架与底层构架的分离上又与WebWork2 存在着一定差距,Spring 的MVC 与Servlet API 相耦合,难于脱离Servlet容器独立运行,在这点的扩展性上,比Webwork2稍逊一筹。Spring对于Web应用开发的支持,并非只限于框架中的MVC部分。即使不使用其中的MVC实现,我们也可以从其他组件,如事务控制、ORM模板中得益。同时,Spring也为其他框架提供了良好的支持,很容易就可以将Struts与Spring搭配使用。因此,对于Spring在Web应用中的作用,应该从一个更全面的角度出发。
J2EE系统采用三层的MVC架构之后,其解决的主要问题无外乎以下几部分:
(1) 将Web页面中的输入元素封装为一个(请求)数据对象。
(2) 根据请求的不同,调度相应的逻辑处理单元,并将(请求)数据对象作为参数传入。
(3) 逻辑处理单元完成运算后,返回一个结果数据对象。
(4) 将结果数据对象中的数据与预先设计的表现层相融合并展现给用户或将其持久化。
这样的J2EE系统将具有下以几个优点:
(1) 多个视图能共享一个模型。在MVC架构中,模型响应用户请求并返回响应数据,视图负责格式化数据并把它们呈现给用户,业务逻辑和表示层分离,同一个模型可以被不同的视图重用,所以大大提高了代码的可重用性。
(2) 模型是自包含的,与控制器和视图保持相对独立,所以可以方便地改变应用程序的数据层和业务规则。由于MVC的三个模块相互独立,改变其中一个不会影响其它的两个,所以依据这种设计思想能构造良好的松耦合的构件。
(3) 控制器提高了应用程序的灵活性和可配置性。
使用MVC需要精心的计划,由于它的内部原理比较复杂,所以需要花费一些时间去理解它。将MVC运用到J2EE应用程序中,会带来额外的工作量,增加应用的复杂性,所以MVC不适合小型应用程序。
面对大量用户界面,业务逻辑复杂的大型应用程序,MVC将会使软件在健壮性,代码重用和结构方面上一个新的台阶,尤其是商业软件的高度可变性。在我实际开发过的项目(一个电子商务网站与一个搜索引擎,和正在开发的一个进销存管理系统)中,我都应用到了Struts,项目相对来说比较成功,通过实践认识到并不是系统中加入了Struts的类库和标签就说明使用了MVC Struts,Struts仅仅是一种思想,你也可以不用它的类库模拟MVC环境,软件的开应该要做到灵活多变。
第三节 分布式架构
J2EE的两大特征就是分层与分布。据统计,大多数中小企业级应用都用不上分布式,因此本文侧重介绍多层,简要的介绍一下分布式的适用范围。
“记住分布式计算机的第一法则:不要分布你的对象!”—(Martin Fowler Patterns of Enterprise Application Architecture)。我们真正需要分布式应用吗?其实我们早已意识到大部分J2EE应用程序,特别是WEB应用程序,并不能从分布式体系架构中受益。甚至相反,由于前期的过渡设计,在根本无需分布式的应用中大量使用分布式技术,不但没有享受到分布式的优点,而且还带来了不同应用层之间昂贵的远程调用,引入了复杂的远程访问期间基础架构和分布式编程。同时,我们也必须明白逻辑层的分层远比物理层的分隔重要。选择分布式也就是选择EJB,选择EJB也就是选择了重量级组件。
也就是说,一些应用选择分布式架构应该有足够的理由。如果真正是明确的业务需求,这属于应有的分布式应用(internal distribution),你需要根据特定的情形选择适当的分布式机制,如果是为了灵活性,在项目的未来某个时期也许要远程输出某些功能,应用程序的架构应该是允许引入选择性的分布式应用(selective distribution),而不是在项目早期就进行过渡设计,直接使用分布式架构。
第四章 数据持久层的设计
第一节 业务对象的持久化
业务对象(Business Object),是对真实世界的实体的软件抽象,它可以代表业务领域中的人,地点,事物或概念。业务对象包括状态和行为,判断一个类是否成为业务对象的一个重要标准,是看这个类是否同是拥有状态和行为。业务对象具有的特征:包含状态和行为,代表业务领域的人,地点,事物或概念。可以重用。通常分为三类业务对象:
(1) 实体业务对象,代表人,地点,事物或概念的组件,在分布式应用中可以作为实体EJB,在更一般的Web应用中可以作为包含状态和行为的Java Bean。
(2) 过程业务对象,代表应用中的业务过程或流程,通常依赖于业务对象。在J2EE应用中,它们通常作为会话EJB或是消息驱动EJB。在非分布式应用中,我们可以使用具有管理和控制应用行为的常规Java Bean。
(3) 事件业务对象,代表应用中的一些事件,如异常,警告或是超时。
业务对象提供了通用的术语概念,不管是技术人员还是非技术人员都可以共享并理解他们,并且它可以隐藏实现细节,对外只暴露接口。
业务对象在Java编程中可以分两类,分别是普通纯Java对象(pure old java object or plain ordinary java object or what ever POJO)和持久对象(Persistent Object PO)。
持久对象实际上必须对应数据库中的entity,所以和POJO有所区别。比如说POJO是由new创建,由GC(垃圾收集器)回收。但是持久对象是insert数据库创建,由数据库delete删除的。持久对象的生命周期和数据库密切相关。另外持久对象往往只能存在一个数据库Connection之中,Connection关闭以后,持久对象就不存在了,而POJO只要不被GC回收,总是存在的。由于存在诸多差别,因此持久对象PO(Persistent Object)在代码上肯定和POJO不同,起码PO相对于POJO会增加一些用来管理数据库entity状态的属性和方法。
持久化意味着通过手工或其化方式输入到应用中的数据,能够在应用结束运行后依然存在,即使应用运行结束或者计算机关闭后,这些信息依然存在,不管什么样的系统都需要数据的持久化。我们将需要持久化处理的BO称之为PO,当应用中的业务对象在内存中创建后,它们不可能永远存在,在从内存中清除之前,要被持久化到关系数据库中。
第二节 数据访问对象设计模式
上一节中提到PO在从内存中清除之前要被持久化到关系数据库中,如何做到这一点呢?OOP是当今的主流,但是不得不使用关系型数据库,因此在企业级开发中,对象-关系的映射(Object-Relation Mapping,简称ORM)就是很关键的一部分了。围绕ORM和数据持久化的方式,软件领域通常采用一种数据访问对象(Data Access Object,简称DAO)设计模式。DAO模式提供了访问关系型数据库系统所需要的所有操作接口,其中包括创建数据库,定义表,字段和索引,建立表间的关系,更新和查询数据库等。DAO模式将底层数据库访问操作与高层业务逻辑分离开,对上层提供面向对象的数据访问接口(接口的实现通常使用抽象工厂设计模式来实现)。在DAO的实现中,可以采用XML语言来配置对象和关系型数据之间的映射。在映射数据库表时,值对象类及其子类所构成的树形结构被用来映射一个数据库表,该继承树通过XML 配置文件对应数据库中的单个表,这使得底层的关系型的数据库表结构能够面向对象模型所隐藏,另外,由于面向对象设计方法中类的可继承性,采用继承树对应一个表的策略使得该映射策略极易扩展,并且能够将一个复杂的数据表转化成若干简单的值对象来表示,提高了系统的可维护性和可修改性。
对于一般的J2EE应用,可以直接通过JDBC编程来访问数据库。JDBC可以说是访问持久层最原始,最直接的方法。在企业级应用开发中,可以通过JDBC编程,来开发自己的DAO API,将数据库访问期间操作封装起来,供业务层统一调用。因此DAO所封装的接口在团队开发中显得非常的重要,几乎是项目成败的关键。
DAO模式在系统中所处的位置
DAO的实现通常使用工厂方法模式,工厂方法模式将创建实例的工作与使用实例的工作分开,也就是说,让创建实例所需要的大量初始化工作从简单的构造函数中分离出去。只需要调用一个统一的方法,即可根据需要创建出各种对象的实例,对象的创建方法不再用编码到程序模块中,而是统一编写在工厂类中。这样在系统进行扩充修改时,系统的变化仅存在于工厂类内部,而绝对不会对其他对象造成影响。
第三节 ORM框架的原理和重要性
如果数据模型非常复杂,那么直接通过JDBC编程来实现持久化框架需要有专业的知识,这样可以直接使用采用第三方提供的持久化框架。如Hibernate,Ibatis,JDO。
在使用ORM框架时也需要遵守OOP设计原则和MVC框架的基本原则,都应该确保框架没有渗透到应用中,应用的上层组件应该和ORM框架保持独立。ORM追求的目标就是要 PO在使用上尽量和POJO一致,对于程序员来说,他们可以把PO当作POJO来用,而感觉不到PO的存在。ORM框架就是要做到维护数据库表记录的PO完全是一个符合Java Bean规范的POJO,没有增加别的属性和方法。
同是ORM框架,但不同的ORM工具实现的方式有所不同,目前主要有两种ORM工具竞争,JDO与Hibernate,以下介绍一下它们的工作机理。
JDO的实现ORM方法如下:
(1) 编写POJO。
(2) 编译POJO。
(3) 使用JDO的一个专门工具,叫做Enhancer,一般是一个命令行程序,手工运行,或者在ant脚本里面运行,对POJO的class文件处理一下,把POJO替换成同名的PO。
(4) 在运行时运行的实际上是PO,而不是POJO。
该方法有点类似于JSP,JSP也是在编译期被转换成Servlet来运行的,在运行期实际上运行的是Servlet,而不是JSP。
Hibernate的实现方法如下:
(1) 编写POJO,通常我们可以通过Hibernate官方提供的MiddleGen for Hibernate 和Hibernate Extension工具包,很方便的根据现有数据库,导出数据库表结构,生成XML格式的ORM配置文件和POJO。
(2) 编译POJO。
(3) 直接运行,在运行时,由Hibernate的cglib库利用Java的反射技术动态把POJO转换为PO。
由此可以看出Hibernate是在运行时把POJO的字节码转换为PO的,而JDO是在编译期转换的。一般认为JDO的方式效率会稍高,毕竟是编译期转换嘛。但是Hibernate的作者Gavin King说cglib(Hibernate类库中的一个必需使用的jar包)的效率非常之高,运行期的PO的字节码生成速度非常之快,效率损失几乎可以忽略不计。实际上运行时生成PO的好处非常大,这样对于程序员来说,是无法接触到PO的,PO对他们来说完全透明。可以更加自由的以POJO的概念操纵PO。Hibernate查询语言(Query Language),即HQL,HQL是一种面向对象的查询语言,不同于SQL(结构化查询语言),它具备继承、多态和关联等特性。
Hibernate是从PO实例中取values的,所以即使Session关闭,也一样可以get/set,可以进行跨Session的状态管理。
在N层的J2EE系统中,由于持久层和业务层和Web层都是分开的,此时Hibernate的PO完全可以当作一个POJO来用,在各层间自由传递,而不用去管Session是开还是关。如果你把这个POJO序列化的话,甚至可以用在分布式环境中。
因此,在较为常用的数据持久方案中,Hibernate的ORM框架是最优秀的,下面是对各种持久方案的比较:
l 流行的数据持久层架构:
Business Layer <-> Session Bean <-> Entity Bean <-> DB
l 为了解决性能障碍的替代架构:
Business Layer <-> DAO <-> JDBC <-> DB
l 使用Hibernate来提高上面架构的开发效率的架构:
Business Layer <-> DAO <-> Hibernate <-> DB
通过实际开发和测试,分析出以上三个架构的优缺点:
(1) 内存消耗:采用JDBC的架构无疑是最省内存的,Hibernate的架构次之,EJB的架构最差。
(2) 运行效率:如果JDBC的代码写的非常优化,那么JDBC架构运行效率最高,但是实际项目中,这一点几乎做不到,这需要程序员非常精通JDBC,运用Batch语句,调整PreapredStatement的Batch Size和Fetch Size等参数,以及在必要的情况下采用结果集cache等等。而一般情况下程序员是做不到这一点的。因此Hibernate架构表现出最快的运行效率。EJB的架构效率会差的很远。
(3) 开发效率:在有Eclipse、JBuilder等开发工具的支持下,对于简单的项目,EJB架构开发效率最高,JDBC次之,Hibernate最差。但是在大的项目,特别是持久层关系映射很复杂的情况下,Hibernate效率高的惊人,JDBC次之,而EJB架构很可能会失败。
在我工作时所做过的一些项目的过程中,例如电子商务网站 (通常是在写数据库查询和存储过程接口时),面对大量的数据反复存储调用,工作量之大无法忍受,这个很烦恼的持久层开发的问题一直在困扰我。持久层的开发现在一般来说要么用CMP,要么用JDBC+DAO。 CMP需要的成本太高,对于这种无需对象分布的系统并不实用,而JDBC+DAO也存在很多的困难,尤其是数据库表很庞大关系很复杂(通常的商业软件的数据库都非常的庞大)的时候,我很难做到把关系表记录完整的映射到PO的关系上来,这主要体现在多表的关系无法直接映射到对持久对象的映射上来,可能是一个表映射多个持久对象,有可能是多个表映射一个PO,更有可能的是表的某些字段映射到一个持久对象,但是另外一些字段映射到别的持久对象上。而且即使这些问题都处理好了,也不能直接按照对象的方式来对持久对象(PO)编程,因为存在1:N关系的持久对象的查询其实就是1+n次对数据库的SQL,等于是完全抛弃了对象设计,完全是按照表字段进行操作,但在这种情况下重复编程量简直无法想象。更重要的是这样做会为系统的成败留下非常大的隐患,因为一般系统是从需求设计,系统设计这样自顶而下的,如果都到了详细设计阶段被持久层映射问题限制,不得不自底向上修改设计方案,又回到了过程化设计上来,严重了违背了依赖倒转原则(DIP)。 很明显我并不是第一个遇到这种问题的人,其实这是一个经典的问题:对象和关系的映射问题。自从OOP流行以来,就一直存在这个难题,所以才有人提出将关系数据库进行重新设计,也会有对象型数据库的出现,但实际上关系数据库并没有被淘汰,于是就只能在上层的应用层找解决方案。ORM产品正是为这种解决方案而设计的。 一个好的ORM应该有如下的特点:
(1) 开源和免费的License,任何人都可以在需要的时候研究源代码,改写源代码,进行功能的定制。
(2) 轻量级封装,避免引入过多复杂的问题,调试容易,也减轻程序员的负担。
(3) 具有可扩展性,API开放,当功能不够用的时候,用户可以自行编码进行扩展。
(4) 开发者活跃,产品有稳定的发展保障。
Hibernate符合以上ORM的标准,它的文档也是非常有特色的地方(而且提供了简体中文版),它不仅仅是 Hibernate的功能介绍那么简单,它实际上是一个持久层设计的最佳实践的经验总结,文档里面的例子和总结全部都是最佳设计的结晶。SUN公司最近发布的EJB3.0中的实体EJB仿效了Hibernate这种轻量级组件技术。
第四节 数据持久层
数据持久化问题关键在于它的复杂性。 复杂性是应用开发过程中最令人头疼的一个问题。每当在一个应用中增加一个功能时,它的复杂性通常呈几何级的增长。这种复杂性往往导致程序的开发无法再继续下去。
专家将应用开发过程产生的复杂性分为两类,即非本质的(accidental) 和本质的(essential)。本质的复杂性是对于解决目标问题所必然产生的复杂性,非本质的复杂性是由于选择了不适当的开发工具和设计工具而产生的复杂性。对于一个功能确定的程序来讲,本质的复杂性是确定的,而非本质的复杂性则是没有限制的。因此,一个应用的开发要想较顺利地取得成功,就需要尽可能地减少非本质的复杂性。
设计模式使人们可以更加简单方便地复用成功的设计和体系结构。将已证实的技术表述成设计模式,也会使新系统开发者更加容易理解其设计思路。
衡量一个系统优秀与否的关键因素,除了能够满足用户需求外还有如下方面:首先是灵活性。灵活性意指这种结构或模式不依赖于任何实际应用,应该与操作系统、应用程序无关。提供独立的结构,可以提供最大的重用。其次是可扩展性。随着业务的扩展,新的业务不断增加,业务逻辑自然增加,系统必然会进行修改或添加相应 功能模块。再次是可配置性。最后是安全性。
数据持久层的设计采纳了多种设计模式,最大限度的降低了系统内部各模块、子系统间的耦合性,使得系统相对易于扩展,并且能够在进行改变时,保证持久层的业务逻辑层相对稳定,基本不需要因持久层的调整改变而进行逻辑层的变动。
根据数据源不同,数据访问也不同。根据存储的类型(关 系数据库、面向对象数据库等)和供应商不同,持久性存储(比如数据库)的访问差别也很大。当业务组件或表示组件需要访问某数据源时,它们可以使用合适的 API来获得连接性,以及操作该数据源。但是在这些组件中包含连接性和数据访问代码会引入这些组件及数据源实现之间的紧密耦合。组件中这类代码依赖性使应用程序从某种数据源迁移到其它种类的数据源将变得非常麻烦和困难,当数据源变化时,组件也需要改变,以便于能够处理新类型的数据源。
数据持久层通过调整抽象工厂(Abstract Factory)模式和工厂方法(Factory Method) 模式,使DAO模式达到了很高的灵活度。 数据持久层使用数据访问对象来抽象和封装所有对数据源的访问。DAO管理着与数据源的连接以便于检索和存储数据,DAO实现了用来操作数据源的访问机制,内部封装了对 Hibernate数据操纵、事务处理、会话管理等API的封装。外界依赖于DAO的业务组件为其客户端使用DAO提供了更简单的接口,DAO完全向 客户端隐藏了数据源实现细节。由于当低层数据源实现变化时,DAO向客户端提供的接口不会变化,采用该设计模式允许DAO调整到不同的存储模式,而不会影 响其客户端或业务组件,即使将来不再采用Hibernate作为关系映射框架,上层客户端也不会受到任何影响。另外,DAO还充当组件和数据源之间的适配 器的角色。
当底层存储随着实现的变化而变化时,该策略可以通过使用抽象工厂模式实现。抽象工厂可以基于工厂方法实现而创建,并可使用工厂方法实现。该策略提供一个DAO的抽象工厂对象,其中该对象可以构造多种类型的具体的DAO工厂,每个工厂支持一种不同类型的持久性存储实现。一旦你获取某特定实现的具体DAO工厂,可以使用它来生成该实现中所支持和实现的DAO。
第五节 数据库连接池、缓存及系统性能的提升
通过使用框架和设计模式对数据访问的封装,极大的提高了软件的可扩展可维护性,同时也带来了系统的性能问题,J2EE系统通过以下一些技术解决这些问题。
缓存(Cache),对于数据库来说,厂商的做法往往是在内存中开辟相应的区域来存储可能被多次存取的数据和可能被多次执行的语句,以使这些数据在下次被访问时不必再次提交对DBMS的请求和那些语句在下次执行时不必再次编译。同样,数据持久层采用缓存技术来保存已经从数据库中检索出来的部分常用数据。客户端访问持久层时,持久层将首先访问缓存,如果能够命中则直接从缓存中提取数据,否则再向数据库发送提取数据的指令。这种设计能够大幅度地提高数据访问速度。
数据库连接池(Connection Pool),池是一个很普遍的概念,和缓冲存储有机制相近的地方,都是缩减了访问的环节,但它更注重于资源的共享。对于访问数据库来说,建立连接的代价比较昂贵,因此,数据持久层建立了“连接池”以提高访问的性能。数据持久层把连接当作对象,整个系统启动后,连接池首先建立若干连接,访问本来需要与数据库连接的区域,都改为和池相连,池临时分配连接供访问使用,结果返回后,访问将连接交还。这种设计消除了JDBC与数据源建立连接的延时,同时在应用级提供了对数据源的并发访问。
享元模式(Flyweight),面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得很庞大,比如,数据库中的记录,如果以每条记录作为一个对象,提取几千条记录,对象数就是几千,这无疑相当耗费内存。数据持久层依据享元模式设计了若干元类,封装可以被共享的类。这种设计策略显著降低了系统的内存消耗。
第六节 Java反射技术
在不了解的情况下要想操纵一个Java对象,需要使用Java的反射技术。反射技术的出现为Java开发工具和服务容器项目的开发提供了新的技术手段,例如第四章中提到的Hibernate在运行时使用cglib类利用Java反射技术动态地将POJO转化为PO。
Java的J2SE开发包中包含一个特殊的部分:Java反射接口,被放置在java.lang.reflect包中。反射使Java的类,包和接口实现了自我描述和动态操作的功能,这些功能对于Java应用程序的开发者来说,似乎过于基础化了,但是反射提供的不仅仅是是一项功能那么简单,通过反射,Java提供了新的一种非直接参与调用的方式,打破了一些固有的限制。反射已经为多个成功的项目所运用,产生了出乎意料的成功效果。配合一些把基础的反射功能扩展的工具项目,Java的动态功能得以出色地发挥出来。Hibernate也得益于反射,反射使Hibernate的代码更简洁有效,在操作时增强了功能。
反射技术机制使很多原来困难的任务变得容易实现了,反射技术的功能很强大,但是开发者在实际的开发过程中,并不一定非要使用反射技术不可,需要根据具体的情形来选择技术方案。即使一定要使用反射技术,也需要一些已有的工具的支持。
第五章 J2EE架构中各层的数据表示
第一节MVC三层体系结构中的数据表示要求
三层体系结构中的数据表示首先要做到的是数据的低耦合度,以Struts三层体系结构为例,三层数据的表示应该如下:
- Web层的数据表示是ActionFormBean,数据来源于html Form post
- 持久层的数据表示是PO,其数据来源于数据库,持久层的数据表示例如CMP
按照MVC的设计原则,层与层之间应该保持相对独立,数据的传递在不同的层之间通常利用Java Bean来创建数据传输对象(Data Transfer Object,简称DTO),从技术的角度上面来说,采用DTO来传输数据可以减少传输数据的冗余,提高传输效率,更重要的是实现了各个层之间的独立,使每个层分工明确。模型层负责业务逻辑,视图层负责向用户展示模型状态。采用DTO,模型层对视图层屏蔽了业务逻辑细节,向视图层提供可以直接显示给用户的数据。在一个规范的J2EE架构中,不同层的数据表示应该被限制在层内,而不应该扩散到其它层,这样可以降低层间的耦合性,提高J2EE架构整体的可维护性和可扩展性。比如说Web层的逻辑进行了修改,那么只需要修改Web层的Form Bean结构,而不需要触动业务层和持久层的代码修改。同样的,当数据库表进行了小的调整,那么也只需要修改持久层数据表示,而不需要触动业务层代码和Web层代码。
先来谈谈ActionFormBean和持久层的PO之间的重大区别:在简单的应用中,ActionFormBean和PO几乎是没有区别,所以很多人干脆就是用ActionFormBean来充当PO,于是 ActionFormBean从JSP页面到Servlet控制层再到业务层,然后穿过持久层,最后一直映射到数据库表。系统以后需要修改其工作量无法想像。但是在复杂的应用中,ActionFormBean和PO是分离的,它们也不可能一样。ActionFormBean是和网页里面的Form表单一一对应的,Form里面有什么元素,Bean里面就有什么属性。而PO和数据库表对应,因此如果数据库表不修改,那么PO也不会修改,如果页面的流程和数据库表字段对应关系不一致,那么又如何能够使用ActionFormBean来取代PO呢?例如一个用户注册页面要求注册用户的基本信息,因此HTML Form里面包含了基本信息属性,于是你需要一个ActionFormBean来一一对应,每个Bean属性对应一个文本框或者选择框等。而用户这个持久对象的属性和ActionFormBean有什么不同呢?它会有一些ActionFormBean所没有的集合属性,如用户的权限属性,用户的组属性,用户的帖子等等。另外还有可能的是在ActionFormBean里面有3个属性,分别是用户的First Name, Middle Name, Last Name,或者干脆在User这个持久对象中就是一个 Name 对象属性。假设注册页面原来只要提供First Name,那么ActionFormBean就这一个属性,后来要提供全名,要改ActionFormBean,加两个属性。但是这个时候PO是不应该修改,因为数据库没有改。
第二节 J2EE系统中各层数据表示的设计
在一个完整的N层J2EE系统中应该如何进行合理数据表示设计呢?Struts是这样做的:
JSP(View) ---> Action Form Bean (Module) ---> Action(Control)Action
Form Bean是Web层的数据表示,它和HTML页面Form对应,只要Web页面的操作流程发生改变,它就要相应的进行修改,它不应该也不能被传递到业务层和持久层,否则一旦页面修改,会一直牵连到业务层和持久层的大面积的代码进行修改,Action就是它的边界。
Action(Web Control) ---> Business Bean ---> DAO ---> ORM --->DB
PO则是业务层和持久层的数据表示,它在业务层和持久层之间进行流动,它不应该也不能被传递到Web层的View中去,而ActionServlet就是它的边界。
再来看看整个架构的数据流程:当用户通过浏览器访问网页,提交了一个页面。于是Action得到了这个Form Bean,它会把Form Bean属性读出来,然后构造一个PO对象,再调用业务层的Bean类,完成了注册操作,重定向到成功页面。而业务层Bean收到这个PO对象之后,调用DAO接口方法,进行持久对象的持久化操作。当用户查询某个会员的信息的时候,他用全名进行查询,于是Action得到一个UserNameFormBean包括了3个属性,分别是first name, middle name, last name,然后Action把UserNameFormBean的3个属性读出来,构造Name对象,再调用BO,把Name对象传递给BO,进行查询。BO取得Name(注意: Name对象只是User的一个属性)对象之后调用DAO接口,返回一个User的PO对象,注意这个User不同于在Web层使用的UserFormBean。然后BO把User对象返回给Action。Action得到User对象之后,把User的基本属性取出,构造UserFormBean,然后把UserFormBean request.setAttribute(…),然后重定向到查询结果页面。查询页面拿到request对象里面的ActionFormBean,自动调用tag显示。
第六章 设计模式的概念与几种常用的J2EE设计模式
第一节 设计模式的概念
程序设计是思维具体化的一种方式,是思考如何解决问题的过程,设计模式是在解決问题的过程中,一些良好思路的经验集成。最早提到设计模式,人们总会提到Gof的著作,它最早将经典的23种模式集合在一起说明,对后期学习程序设计,尤其是对从事物件导向程序的人们起了莫大的影响。对于Java设计师来说,源代码的汇聚按尺度来分,可以分为由Java语句组成的“代码模式”,由Java类和对象组成的“设计模式”,由大尺度的构件组成的“架构模式”。
Gof 提到的OOP中设计模式涵盖三大类。
(1) 创建Creational 模式
对象的产生需要消耗系统资源,所以如何有效率的产生、管理与操作对象,一直都是值得讨论的课题, Creational 模式即与对象的建立相关,在这个分类下的模式给出了一些指导原则及设计的方向。
l 简单工厂(Simple Factory) 模式
l 抽象工厂(Abstract Factory) 模式
l 建造(Builder) 模式
l 工厂方法(Factory Method) 模式
l 原始模型(Prototype) 模式
l 单例(Singleton) 模式
l 多例(Multition)模式
(2) 结构Structural 模式
如何设计对象之间的静态结构,如何完成物件之间的继承、实现与依赖关系,这关系到系统设计出来是否健壮(robust):是否易懂、易维护、易修改、耦合度低等。Structural 模式正如其名,其分类下的模式给出了在不同场合下所适用的各种对象关系结构。
l 缺省适配(Default Adapter) 模式
l 适配(Adapter)模式
l 桥梁(Bridge) 模式
l 合成(Composite) 模式
l 装饰(Decorator) 模式
l 门面(Façade) 模式
l 享元(Flyweight) 模式
l 代理(Proxy) 模式
(3) 行为Behavioral 模式
对象之间的合作行为构成了程序的最终行为,对象之间若有良好的行为互动,不仅使得程序执行时更有效率,更可以让对象的职责更为清晰、整个程序的动态结构(对象之间的互相调用)更有弹性。
l 责任链(Chain of Responsibility) 模式
l 命令(Command) 模式
l 解释器(Interpreter) 模式
l 迭代子(Iterator) 模式
l 调停者(Mediator) 模式
l 备忘录(Memento) 模式
l 观察者(Observer) 模式
l 状态(State) 模式
l 策略(Strategy) 模式
l 模版方法(Template Method) 模式
l 访问者(Visitor) 模式
第二节 工厂创建模式与单例模式
l 简单工厂(Simple Factory)模式:又称静态工厂方法模式(Static Factory Method Pattern)
l 工厂方法(Factory Method)模式:又称多态性工厂(Polymorphic Factory)模式或虚拟构造子(Virtual Constructor)模式
l 抽象工厂(Abstract Factory)模式:又称工具箱(Toolkit)模式
下面以简单工厂设计模式为例说明代码模式在Java编程中的应用。
从上图可以看出,简单工厂模式涉及到工厂角色,抽象产品角色以及具体产品角色等三个角色:
l 工厂类(Creator)角色:担任这个角色的是工厂方法模式的核心,含有与应用紧密相关的商业逻辑,工厂类在客户端的直接调用下创建产品对象,它往往由一个具体Java类来实现
l 抽象产品(Product)角色:担任这个角色的类是由工厂方法模式所创建的对象的父类,或它们共同拥有接口。抽象产品角色可以用一个Java接口或者Java抽象类实现
l 具体产品(Concrete Product)角色:工厂方法模式所创建的任何对象都是这个角色的实例,具体产品角色由一个具体Java类实现
例如我们要在程序中产生两个对象:一个圆形与一个方形,建立时要同时设定它们的中心位置,然后它们会负责画出自己。我们可以设计一个 Shape Factory工厂类,负责创建指定的对象,调用者只管指定对象名称与中心位置,而不管这些对象如何产生,对象生成后的中心位置设定被隐藏于 Shape Factory工厂类中。
如上图所示的,Main代表了客户的角色,它只依赖于表层业务调用,而不关心特定的实例,实例的细节由Shape Factory完成,我们以一个简单的程序来实现上面这个UML类图:
public interface IShape { //建立IShape接口
public void setCenter(int x, int y);
public void draw(); //定义一个接口方法
}
public class Circle implements IShape {
private int x;
private int y;
public void setCenter(int x, int y) {//实现IShape接口setCenter方法
this.x = x;
this.y = y;
}
public void draw() { //实现IShape接口draw()方法
System.out.println("Circle center at ("
+ x + ", " + y + ")");
}
}
public class Square implements IShape {
private int x;
private int y;
public void setCenter(int x, int y) { //实现IShape接口setCenter方法
this.x = x;
this.y = y;
}
public void draw() {//实现IShape接口draw()方法
System.out.println("Square center at ("
+ x + ", " + y + ")");
}
}
public class ShapeFactory {
public static IShape createShapeAt(String name, int x, int y) {
try {
IShape shape
= (IShape) Clas.forName(name).newInstance();//使用Java反射技术获得名为name产品实例,并让IShape接口持有此实例对象
shape.setCenter(x,y);
return shape;
}
catch(Exception e) {
System.out.println(
"Sorry! No such class defined!"); return null;
}
}
}
public class Main {
public static void main(String args[]) {
// 产生一个圆形并且显示它
ShapeFactory.createShapeAt("Circle", 10, 10).draw();
System.Out.println();
// 产生一个方形并显示它
ShapeFactory.createShapeAt("Square", 20, 25).draw();
}
}
客户只要面对Factory,客户依赖于产品的调用介面,产品的具体实例是可以与客户隔开的,它们也是可以互换的。简单工厂模式是工厂方法模式与抽象工厂模式的一种特殊情况,关于它们的实现可以参考相关资料。
单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式在系统设计时使用非常广泛,常用的资源管理器一般使用此模式,在一些线程安全的情况下使用也比较多,如数据库连接池访问接口,属性文件管理等。
以上面的UML图为例来说明单例模式的使用方法,我们可以在第一次需要实例时再创建对象,也就是采用所谓的Lazy Initialization懒汉式:
public class Singleton {
private static Singleton instance = null;
private Singleton() {
// .......
}
public static Singleton getInstance() { //获得系统唯一实例的静态方法
if (instance == null) instance = new Singleton();
return instance;
}
// ...... 其它代码
}
上面的代码适用于单线程的程序,在多线程的程序下,以下的写法在多个线程的竞争资源下,将仍有可能产生两个以上的对象,存在线程安全性问题,例如下面的情况:
Thread1: if(instance == null) // true
Thread2: if(instance == null) // true
Thread1: instance = new Singleton(); // 产生一个实例
Thread2: instance = new Singleton(); // 又产生一个实例
Thread1: return instance; // 返回一个实例
Thread2: return instance; // 又返回一个实例
在多线程环境下,为了了避免资源同时竞争而导致如上产生多个实例的情况,我们加上同步机制:
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
synchronized static public Singleton getInstance() {//为获得系统唯一实例的静态方法加上线程同步机制,保证此方法同时只能被一线程调用
if (instance == null) instance = new Singleton();
return instance;
} }
第三节 使用工厂模式实现DAO
由于J2EE模式众多,篇幅有限,这里只概要介绍其中的一种使用工厂模式实现数据访问对象。
使用数据访问对象(DAO)来抽象和封装所有对数据源的访问。 DAO管理着与数据源的连接以便于检索和存储数据,DAO实现了用来操作数据源的访问机制。依赖于DAO的业务组件为其客户端使用DAO提供了更简单的接口,DAO完全向客户端隐藏了数据源实现细节。由于当低层数据源实现变化时,DAO向客户端提供的接口不会变化,所以该模式允许DAO调整到不同的存储模式,而不会影响其客户端或业务组件。重要的是,DAO充当组件和数据源之间的适配器。
当低层存储随着实现的变化而变化时,策略可以通过使用抽象工厂模式而实现。抽象工厂可以基于工厂方法实现而创建,并可使用工厂方法实现,该策略提供一个 DAO的抽象工厂对象,其中该对象可以构造多种类型的具体的DAO工厂,每个工厂支持一种不同类型的持久性存储实现。一旦你获取某特定实现的具体DAO工厂,你可以使用它来生成该实现中所支持和实现的DAO,如下面类图所示。
例如在一个项目中有四张表,分别是用户信息表(user Info),论坛表(Forum),主题表(Topic),回复表(Reply)。先创建与数据库表相对应组件Java Bean,然后为每一个数据库表配置一个数据库操作接口。如下所示:
UserInfo访问对象接口
package com.webclass.dao;
public interface UserInfoDao {
//设置帖子属性时,用户的属性
public void upUserState(String state,String usrName);
// 查询用户是否存在判断(返回整个用户)
public UserInfo selectUser(String usrName);
//设置用户权限
public void upUserRole(int i ,String usrRole,int usrId);
//修改用户信息
public boolean editUser(UserInfo user);
……………………………等操作逻辑方法
}
Forum访问对象接口
package com.webclass.dao;
public interface ForumDao {
…………………操作逻辑方法
}
Reply访问对象接口
package com.webclass.dao;
public interface ReplyDao {
………………操作逻辑方法
}
Topic访问对象接口
package com.webclass.dao;
public interface TopicDao {
………………操作逻辑方法
}
接下来就是写具体类实现以上其接口,而接口方法不依赖于类的具体实现。数据库的访问对象已经创建好之后,应用程序将调用这些对象进行数据库操作,如何调用这些对象呢?我们使用一个工厂类来专门负责这些对象的创建工作:
public class DBFactory {
public static ForumDao dBForum=null;
public static ReplyDao dBReply=null;
public static TopicDao dBTopic=null;
public static UserInfoDao dBUserInfo=null;
synchronized public static ForumDao getDBForum(){
if(dBForum==null) dBForum=new DBForum();
return dBForum;
}
synchronized public static ReplyDao getDBReply(){
if(dBReply==null) dBReply=new DBReply();
return dBReply;
}
synchronized public static TopicDao getDBTopic(){
if(dBTopic==null) dBTopic=new DBTopic();
return dBTopic;
}
synchronized public static UserInfoDao getDBUserInfo(){
if(dBUserInfo==null) dBUserInfo=new DBUserInfo();
return dBUserInfo;
}
}
这样应用程序的上层组件就可以直接调用DBFactory工厂对象所持有的静态对象来进来数据库操作了,更重要的是当底层数据库访问操作发生改变时只需修改访问对象,而不会波及到上层组件,使用软件健壮性和灵活性大大提高。
总结
即使是利用当今最先进的软件平台J2EE,开发企业应用程序仍然是一个难题,因为软件的复杂性和脆弱性无法回避。J2EE通过J2EE API提供了技术与服务的高层抽象,使企业开发得到了一定的简化。但是,仅仅知道J2EE API是不够的。要设计良好的体系结构,得到高质量的应用程序,要知道何时如何正确的使用J2EE API,就要使用更为实用的方式。
计算机技术更新发展很快,新技术方面由于经验缺乏,通常我们要自己猜测如何正确使用这些技术,要通过不断的试验,直到找出最佳的方法,最佳的方法显然是从实践中得到的,不是发明出来的,而是发现和不断完善的。
工程学产中的一大原则就是总结经验和利用实践证明行之有效的方案,软件开发也是这样。经验有助于更快更顺利的建立良好的解决方案,从而节省成本,提高质量。唯一的问题就是要需要获得经验,但这个经验可以从别人那里间接获得,而不一定需要自己的直接经验。别人的经验如何描述?多年来,模式已经成为收集,规范和分析某些情境中常见问题的有效方法,学习模式可以节省自己的时间,知道如何根据许多开发人员的间接经验进行合理的设计。
结束语
在撰写这篇论文期间,我查阅了大量的技术书籍和文章,学到了很多的知识,使自己的技术水平得到了提高。同时得到了袁健美老师的悉心指导和帮助,在此,我向袁健美老师表达诚挚的谢意。
参考文献:
1. Bruce Eckel. Think in Java第三版 .电子工业出版社. 北京. 2002
2. Khawar Zaman Ahmed. J2EE和UML开发Java企业级应用程序. 清华大学出版社. 2003
3. Crig Bery. 实用J2EE设计模式编程指南. www.china-pub.com. 2003
4. 孙卫琴. Tomcat与Java Web开发技术. 电子工业出版社. 2004
5. 孙卫琴. 精通Struts. 电子工业出版社. 2004
6. Christopher Alexande. The Timeless Way of Building. Arrangement with Oxford University Press, Inc. 2003
7. 夏昕. Hibernate开发指南. www.china-pub.com. 2004