`
fangang
  • 浏览: 876460 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
311c4c32-b171-3767-b974-d26acf661fb2
谈谈用例模型的那些事儿
浏览量:38618
767c50c5-189c-3525-a93f-5884d146ee78
一次迭代式开发的研究
浏览量:68797
03a3e133-6080-3bc8-a960-9d915ed9eabc
我们应当怎样做需求分析
浏览量:409824
753f3c56-c831-3add-ba41-b3b70d6d913f
重构,是这样干的
浏览量:91339
社区版块
存档分类
最新评论

如何在struts+spring+hibernate的框架下构建低耦合高内聚的软件

阅读更多
一.问题的提出

我常常在思考一个问题,我们如何能设计出高水平、高质量的软件出来。怎样是高水平、高质量的软件?它应当是易于维护、易于适应变更、可重用性好的一个系统。如何做到这一点呢?答案当然是“低耦合、高内聚”了。低耦合就是软件在构造的时候,各个模块、各个功能、各个类都不会过度依赖于它周围的环境。只有这样,才能使我们的模块(功能、类)在周围发生变更时不受影响,做到易于维护和易于适应变更。正因为如此,也使它更易于重用到其它功能类似的环境中,提高了重用性。高内聚则使软件中的各个模块(功能、类)能够各尽其能而又充分合作,也就是对于软件问题空间中需求的各个功能,系统可以合理地把它分配给各个模块(功能、类)来共同完成,而不是一个或几个八面玲珑、包打天下的超级类一个人完成。而对于该系统中的某一个模块(功能、类),具有自己高度相关的职责,即该职责中的几个任务是高度相关的。每一个模块(功能、类)都决不去完成与自己无关职责的任务。

那么怎样能构造一个低耦合、高内聚的系统呢,时下最流行的框架结构之一的struts+spring+hibernate为我们提供了方便。使用struts我们可以应用MVC模型,使页面展现与业务逻辑分离,做到了页面展现与业务逻辑的低耦合。当我们的页面展现需要变更时,我们只需要修改我们的页面,而不影响我们的业务逻辑;同样,我们的业务逻辑需要变更的时候,我们只需要修改我们的java程序,与我们的页面无关。使用spring我们运用IoC(反向控制),降低了业务逻辑中各个类的相互依赖。假如类A因为需要功能F而调用类B,在通常的情况下类A需要引用类B,因而类A就依赖于类B了,也就是说当类B不存在的时候类A就无法使用了。使用了IoC,类A调用的仅仅是实现了功能F的接口的某个类,这个类可能是类B,也可能是另一个类C,由spring的配置文件来决定。这样,类A就不再依赖于类B了,耦合度降低,重用性提高了。使用hibernate则是使我们的业务逻辑与数据持久化分离,也就是与将数据存储到数据库的操作分离。我们在业务逻辑中只需要将数据放到值对象中,然后交给hibernate,或者从hibernate那里得到值对象。至于用Oracle、MySQL还是SQL Server,如何执行的操作,与我无关。

然而我要说的是,即使我们使用了struts+spring+hibernate框架构建我们的软件,就可以做到“低耦合、高内聚”了吗?我认为这是远远不够的!我认为我们在使用struts+spring+hibernate框架的时候常常会有以下几个问题值得改进。

二.分析与解决

1.编写DAO的时候不要直接去使用hibernate或spring对hibernate的支持。

现在我们在编写DAO的时候普遍都是直接继承spring对hibernate的封装类HibernateDaoSupport,然后使用该类提供的诸如saveOrUpdate(), saveOrUpdateCopy(), find()等等。另外,在使用excute()方法实现一些更复杂的hibernate功能的时候还会使用hibernate的类,诸如Query, Session, Type等。这样直接使用spring和hibernate的类存在的问题在于,你的代码将不得不依赖与spring和hibernate的某个版本。比如说,现在hibernate3出来了,改动挺大,实际上最要命的是包结构,hibernate2的包结构是net.sf.hibernate.*,然而hibernate3是org.hibernate.*。同样,spring为了支持hibernate3,包名也改为org.springframework.orm.hibernate3.*。假如,你现在新开发一个项目,这没什么关系,如果是升级一个项目问题就来了。如果你希望将你的一个项目从hibernate2升级为hibernate3,你不得不修改DAO中所有对hibernate和spring-hibernate的引用。如果你的代码中出现hibernate2与hibernate3不兼容的方法和类,比如saveOrUpdateCopy()(在hibernate3中已经没有了),你还将不得不改写。那么你可能会说,我不会这样升级。如果你的软件生命周期有好多年,hibernate升级到4,升级到5,你还是依然使用hibernate2?如果你以这种方式开发一个平台,你能要求所有使用你平台的软件项目都只能使用hibernate2?更进一步说,我现在开发一个产品,今后的客户将是成千上万。经过1、2年我需要升级了,这时我的升级包有几十M,几乎把所有的DAO都换了个遍,这样的升级无异于重装。也许,有人会提出另一个方案,在HibernateDaoSupport与DAO中间增加了一个基础类,这样将基础类中的org.springframework.orm.hibernate.support.HibernateDaoSupport,改为了org.springframework.orm.hibernate3.support.HibernateDaoSupport,这样其下面继承的DAO就不用改动了。然而在源码上是小小的改动,但对于类来说,两个不同版本的HibernateDaoSupport其相关的属性和方法还是有不少变化,那么在基础类重新编译的同时,你的继承类重新编译否。既然已经重新编译了,因此你的所有DAO在升级的时候依然要打入升级包,问题依然存在。

以上问题,究其原因,是我们项目中的DAO依赖于hibernate和spring,因为我们对它们的使用是继承,是一种很强的关联,就是一种依赖。我们只需要稍微进行一些调整,就可以解决这个问题,那就是不使用直接继承,而使用接口进行分离。可以使用Façade模式,先建立一个叫BasicDao的基础类,从名称我们可以看出,它是所有DAO的基础类,实现DAO操作所需的所有诸如save()、delete()、load()、query()等方法,除了一些基本的方法,诸如翻页查询、getCount、解析查询条件形成HQL语句等功能也在这里实现,但是不要使用与hibernate或spring有关的任何方法和类。同时,BasicDao调用一个叫DaoSupport的接口,DaoSupport的接口则是提供持久化所需的基本方法,最原始的元素。然后,我为DaoSupport接口提供各种不同的实现,比如hibernate2的实现DaoSupportHibernateImp、hibernate3的实现DaoSupportHibernate3Imp,整个结构如下图所示。BasicDao可以使用hibernate或spring提供的方法,但是不是直接使用,而是通过调用DaoSupport的实现类来使用。然而BasicDao到底是使用的那个实现类,我们通过spring的IoC,通过配置文件来决定到底使用哪个实现。同时,BasicDao也不要使用诸如SpringContext的类来实现IoC,而是通过建立setDaoSupport()和getDaoSupport()方法,然后在spring配置文件中建立引用。


2.编写Action的时候不要直接使用spring和spring的继承类

前面我说了应当避免DAO引用spring或hibernate及其继承类。同样的事情也发生在Action中。由于Action通常不纳入spring的管理,因此Action在通过spring调用某个BUS的时候,往往是去引用一个叫SpringContext的类(spring的类ContextLoaderServlet的继承类),然后使用它的getBean()方法。如此的使用,我们的Action将依赖与spring。我们同样可以使用一个叫BasicAction的父类,然后用一个接口来隔离spring。由于Action通常不纳入spring的管理,我们通过一个*.property的配置文件来决定接口到底调用哪个实现类。这样的结构的另一个好处是,我们还可以将所有Action都必须使用的诸如写日志、用户校验、异常处理都放在父类BasicAction中,提高系统的可维护性。

3.当BUS需要获取别的模块的数据的时候,不要直接去使用该模块的DAO

我举一个简单的例子:我需要设计一个软件评审的管理软件,该软件分为评审组织者制订评审计划、评审者分别填写评审表后由评审组织者汇总评审表、评审组织者制作评审报告。这是一个非常简单的项目,分成了三个人来完成。但是项目进行快结束的时候却出现了问题。填写评审表需要获得评审计划中的一些数据,制作评审报告的数据来源于评审表。项目组在开始编程前先开了一次会,大家约定好了各个部分的数据格式及其规则,然后开始工作。然而数天后项目组把各个模块整合以后发现,系统根本跑不起来,为什么呢?设计评审计划的人发现,所有评审计划应当按照产品编号来进行管理而不是项目编号。由于这个变更,填写评审表模块在待评审列表中什么都无法显示;同样,设计评审表的人发现,在一个评审计划中评审表与评审者不是一对多的关系,而是一对一的关系,因而修改了这两个表的关联。因为这样,在制作评审报告时就不能正确得到评审表数据。其实一个软件项目在整个进行过程中总是不断变更。我们需要做的不是去抑制这些变更,而应当是通过软件的结构去适应这些变更,即是降低各模块间的依赖(耦合),提高内聚。

拿这个实例来说,当评审表需要调用评审计划的数据的时候,不应当是自己写一个DAO去调用评审计划的数据,而应当是调用评审计划的接口,将这个任务交给评审计划类来完成。当评审报告需要调用评审表的数据的时候,同样应当去调用评审表的接口,由评审表来实现。同时,这种调用应当是去调用BUS层的接口。为什么呢?比如在评审计划中的一个业务逻辑是只有在评审计划发布以后才能制作评审表,那么怎样才是已发布的评审计划呢?这个业务逻辑应当由谁来定义?当然是评审计划。在什么地方定义?当然是BUS而不是DAO,因为DAO仅仅是实现数据的持久化,而BUS才是实现业务逻辑的地方。既然如此,如果评审表去调用评审计划的DAO,那么已发布评审计划的业务逻辑必然包含在了评审表的业务逻辑里了。我们假设有一天,已发布评审计划的业务逻辑发生变更了(实际上这样的会在你毫不经意间就发生了),编写评审计划的人会很快就修改了评审计划的业务实现并且测试通过了。他不知道评审表里也包含了这样的业务逻辑,因而修改后的程序在运行到评审表的时候就很可能会出错。不幸的是,在实际工作中,同样一个业务逻辑可能包含在无数个你可能知道,但你也可能不知道的代码中。这样的结构就是一个不易于维护的差的结构。

三.总结:从技术升级和需求变更两方面适应变化

软件开发专家Alistair Cockburn在《敏捷软件开发》中说过,软件在整个生命周期中变更是无时无刻不发生的。我认为,软件的变更一方面是技术的更新,今天我们使用struts+spring+hibernate,明天呢,我们将使用什么呢?正因为技术变更得太快,我们的系统应当不要太依赖于某个具体的技术或框架,以便于明天的技术更新。同时,来自客户的需求变更也是我们必须面对的另一个压力。一句经典的话是这样描述客户的变更:“当我看到时我的需求就变更了。(I changed just when I saw it.)”过去我们用需求说明书来抑制用户的变更,现在发现不能这样了。敏捷软件开发提出了许多应对用户变更的办法,其中建立低耦合高内聚的软件结构也是办法之一。系统中的所有对象都有自己的明确职责,这个职责应当不多且高度相关。每个对象都应当只完成自己的职责,而把其它的任务交给别人去做。正如我前面提到的例子,评审表对象只完成与评审表相关的操作,而在它需要完成的任务中,需要使用评审计划数据的相关功能,交给评审计划对象去完成,评审表只管调用。这样的构造要求开发者相互协调,彼此多交流,同时,也需要有人来统一规划,站在全局的设计这个系统。通过这些,我们才可以适应变化,提高设计水平。
  • 大小: 40.7 KB
分享到:
评论
45 楼 lwqenter 2012-05-07  
楼主写得好啊,我发现来得很晚,但正是时候,
44 楼 zlsunnan 2007-06-21  
真是受益匪浅啊 自己只知道用 但是从来也没有考虑这些 惭愧啊 向楼主学习
43 楼 fangang 2007-06-20  
kevinming 写道
这样做是使得持久层简单了很多,但我觉得大型开发还是不大妥当。
参数是sql/hql的话,就把这些sql/hql写在业务逻辑层service里面了。我觉得这样做不大合适。我还是喜欢sql/hql全部都在DAO里面,虽然DAO大了,但要统一维护sql/Hql简单很多了。也做到了分层的实际意义,不然你一个DAO提供这些传入sql/hql的方法,那也只不过是一个数据库入口而已吧。

实际上,按照我的理论,在不论DAO或BUS中都不再写任何hql/sql,也不再需要任何表连接。我们执行任何表更新或查询的操作都是针对单个值对象的操作,然而这单个值对象实际上包含了与它相关的所有表的信息。比如“员工”表,其值对象已经包含了它的部门的属性,我们在查询的时候只是查询“员工”对象,不需要将“员工”与“部门”进行关联查询。当然,hibernate在实现的时候当然需要表关联,当对于DAO和BUS根本就不用考虑这些,它们只需要以对象的思维,运用hibernate的一对多、多对一、多对多、继承的关系,去操作值对象就可以了。这也是DAO的基本思想所在。
当然,我们不能否认,hibernate的值对象关系在处理异常复杂的查询和报表的时候是脆弱的。当你在处理这些东西的时候,不论是DAO、值对象、hibernate,还是hql其实都不是好的方案。直接采用sql和jdbc也许更合适一些。
42 楼 kevinming 2007-06-20  
giscat 写道
持久层的接口只需要很少的几把函数就可以了
save(Object) 用于insert
update(object) 用于 update
executeUpdate(sql or hql)
executeQuery(sql or hsql)

不管啥业务,都是以上几把函数的组合,
调用入口多了,就会混乱
四把函数就足够了
没必要为每个实体去写DAO,因为都是通用的



这样做是使得持久层简单了很多,但我觉得大型开发还是不大妥当。
参数是sql/hql的话,就把这些sql/hql写在业务逻辑层service里面了。我觉得这样做不大合适。我还是喜欢sql/hql全部都在DAO里面,虽然DAO大了,但要统一维护sql/Hql简单很多了。也做到了分层的实际意义,不然你一个DAO提供这些传入sql/hql的方法,那也只不过是一个数据库入口而已吧。
41 楼 fangang 2007-06-19  
tsingn 写道
伴随着JDK1.5被广泛使用,利用范性可以很好地解决大量DAOs的问题
可以参考http://www.hibernate.org/328.html

虽然泛型可以简化很多的问题,但不用泛型也可以解决大量DAOs的问题。在我以后的版本中,整个DAO层只有一个BasicDao和它的接口GeniricDao。你可以参考一下我后面写的博客的附件。
40 楼 fangang 2007-06-19  
classicbribe 写道
楼主哪里的??我和你有共同的愿望...设计优秀的软件...我从毕业后做第一个项目就是个边做边想的项目..到现在还是..受够了...拜你为师吧......行吗?行的话 加我QQ:214725178

拜师不敢当,相互交流,我的MSN:fan_gang2004@hotmail.com,没有QQ。
39 楼 classicbride 2007-06-19  
楼主哪里的??我和你有共同的愿望...设计优秀的软件...我从毕业后做第一个项目就是个边做边想的项目..到现在还是..受够了...拜你为师吧......行吗?行的话 加我QQ:214725178
38 楼 youlong05 2007-05-16  
tsingn 写道
伴随着JDK1.5被广泛使用,利用范性可以很好地解决大量DAOs的问题
可以参考http://www.hibernate.org/328.html
好呀。。谢谢
37 楼 tsingn 2007-05-16  
伴随着JDK1.5被广泛使用,利用范性可以很好地解决大量DAOs的问题
可以参考http://www.hibernate.org/328.html
36 楼 qianqian_1216 2007-05-16  
持久层的接口只需要很少的几把函数就可以了
save(Object) 用于insert
update(object) 用于 update
executeUpdate(sql or hql)
executeQuery(sql or hsql)

不管啥业务,都是以上几把函数的组合,
调用入口多了,就会混乱
四把函数就足够了
没必要为每个实体去写DAO,因为都是通用的

我同意,我们可以通过泛型类型绑定,可以多个实体公用一个DAO,大量减少了代码量.
35 楼 fangang 2007-05-16  
JJYAO谈的问题我的理解是对于第三方产品是否需要解藕,这个问题正如我在《(原创)一个优秀软件开发人员的必修课:GRASP(2)低耦合》中提到的,耦合不好,但过度的解藕也不好,关键看你自己的需求。现在我提出解藕,是因为我看到了不同版本的hibernate和spring存在的差异需要我们解藕,以适应各个版本的差异带给我们的影响。
34 楼 JJYAO 2007-05-16  
本质上说,LZ的还是用代码级别的封装,以达到屏蔽具体实现这种古老的方式。做这种封装粒度难以控制,粗了则不够用,细了则极其烦琐,维护成本也很高。想象一下系统会使用如此众多的Third-party的Jar包吧,难道你都要封装?难道只会升级Struts,spring,hibernate?

LZ只提到了产品Non-Functional方面的升级,其实产品很关键的还有Functional的升级,在这里就不谈Functional的升级,
对于Non-Functional的升级,jar包升级是一方面,还有一方面是完全的功能性,甚至是整个实现的升级,比如从传统web升级到RIA。代码封装这种低级别的实现是没有办法做到的。

对于真正的产品公司,一般不会采用低级别的代码封装,从大的方向看,我的建议是
1. 选对你的第三方框架
2. 采用高度抽象的元数据描述系统,采用代码生成方式
3. 做好你的核心引擎
33 楼 fangang 2007-05-15  
zhaonjtu 写道
分析决策的第一点感觉很象桥接模式啊,不知道理解有无错误.

确实比较像桥接模式,但桥接模式是对类中的某个方法进行抽象,即桥接模式能使类在运行过程中动态地确定某个方法的实现,但这似乎还不是本方案的基本目的。
本方案的基本目的是使我们的业务系统不依赖于spring系统,即一个系统与另一个系统的解藕。从这样一个思路来讲更应当理解为一个适配器模式(虽然并不是标准的适配器模式),DaoSupportHibernate3Imp或DaoSupportHibernateImp是那个适配器,业务系统提供DaoSupport接口与外界交流,然后通过那个适配器与spring衔接而又不依赖于它。
32 楼 zhaonjtu 2007-05-15  
分析决策的第一点感觉很象桥接模式啊,不知道理解有无错误.
31 楼 fangang 2007-04-27  
robinjim 写道
看了楼主的例子,收益不少,只是有个疑问DaoSupportHibernateImp里面的query方法调用太繁琐了点,应该是对单表操作,为什么不用Criteria呢,传这么多参数容易产生接口耦合问题

谢谢robinjim的提醒,这确实是个问题
30 楼 robinjim 2007-04-27  
看了楼主的例子,收益不少,只是有个疑问DaoSupportHibernateImp里面的query方法调用太繁琐了点,应该是对单表操作,为什么不用Criteria呢,传这么多参数容易产生接口耦合问题
29 楼 fangang 2007-04-27  
xchlove 写道
这个问题很容易解决,BasicDao(以及其子类们)均实现了某一个接口,DAO们需要使用到的方法在接口中定义好就行了……


不太明白 不是还都要继承吗?为什么可以解决重新编译的问题了?

在类与类的关系中,接口与实现是松耦合的,父类与子类的继承是强耦合的。在我的结构中,BasicDao与spring的HibernateDaoSupport不是直接的继承关系,而是通过DaoSupport这样一个接口,实现了解藕。实现这个解藕是我的根本目的,因为这样就使我的项目不依赖于spring了。至于DaoSupport的各个实现依然继承自HibernateDaoSupport,这已经不重要了,因为对于不同的spring版本,由于它们存在一定地差异,每个版本我都需要去给一个实现,也就是说,当更换了spring和hibernate,我需要重新编译和升级更新的只有DaoSupport的实现和相应的配置文件。
28 楼 xchlove 2007-04-26  
这个问题很容易解决,BasicDao(以及其子类们)均实现了某一个接口,DAO们需要使用到的方法在接口中定义好就行了……


不太明白 不是还都要继承吗?为什么可以解决重新编译的问题了?
27 楼 e3002 2007-04-11  
写的不错!学习
26 楼 justshare 2007-04-11  
楼主与我们现在分层的应用基本相似,业务逻辑的实现通过接口隔离,大概的流程是这样的:action---bussiness---dao---daoImpl.

相关推荐

    Struts+Spring+Hibernate+WebService集成架构.doc

    整个过程中,各层通过定义清晰的接口进行交互,确保了系统的松耦合和高内聚。 综上所述,Struts+Spring+Hibernate+WebService集成架构通过合理的分层设计,充分利用了各框架的优势,构建出了一个既高效又灵活的企业...

    Struts+hibernate+spring框架

    在请求处理过程中,Spring将负责创建和配置这些对象,从而实现了解耦合和高内聚的设计原则。 在MySQL作为数据库的情况下,SSH整合通常会涉及以下步骤: 1. **环境配置**:安装并配置JDK、Apache Tomcat服务器、...

    Struts+Spring+Hibernate+Freemarker新闻系统

    6. **整合应用优势**: 将Struts、Spring、Hibernate和Freemarker整合在一起,可以实现松耦合、高内聚的设计,提高代码的可维护性和可扩展性。此外,这四个框架都有丰富的社区支持和强大的功能,可以应对复杂的应用...

    jar包(struts2.0.8+spring2.0+hibernate3.2)

    这个压缩包“struts2.0.8+spring2.0+hibernate3.2”包含了这三个框架的特定版本,这将帮助开发者在构建基于Java的企业级应用程序时快速搭建环境。 **Struts2** 是一个用于构建企业级Web应用程序的开源MVC框架,它...

    struts+hibernate+spring集成教程

    6. **优势与最佳实践**:集成Struts、Hibernate和Spring可以实现松耦合、高内聚的设计,提高开发效率。在实际项目中,我们应遵循模块化、分层设计的原则,合理划分各层职责,并注意性能优化,如缓存策略、事务隔离...

    struts+hibernate+spring开发实例

    在这个过程中,不仅深入了解了每一层的作用和实现原理,还学习了如何通过这些技术框架来构建一个高内聚低耦合的应用程序。这种分层设计的思想对于未来开发复杂的企业级应用具有重要意义。 此外,实验还展示了如何...

    开发者突击 struts2+Spring+Hibernate 整合开发 投票管理系统

    这种整合能够充分利用各个框架的优势,实现松耦合、高内聚的设计,提高开发效率和代码质量。 首先,Struts2作为前端控制器,接收HTTP请求,解析用户输入,并调用相应的业务逻辑。它的拦截器机制使得我们可以添加...

    做struts2.0+spring+hibernate项目使用的jar包

    通过合理的配置和编程,可以构建出松耦合、高内聚的系统架构。 总的来说,Struts2.0+Spring+Hibernate的组合为Java Web开发提供了一个强大的解决方案,它可以帮助开发者快速地构建出健壮的、模块化的应用,同时也为...

    会员管理系统(struts+hibernate+spring)

    SSH框架结合使用,可以实现松耦合、高内聚的代码结构,提高开发效率和代码可维护性。同时,通过Spring的事务管理,保证了数据操作的原子性和一致性,提升了系统的稳定性。 综上所述,"会员管理系统(struts+...

    Struts+Hibernate+Spring实现的人力资源管理系统

    此外,Spring的集成能力使得Struts和Hibernate能无缝配合,构建出松耦合、高内聚的系统架构。 除了技术选型,本系统还包含了需求文档、数据库设计文档以及需求讲演PPT,这些都是项目开发的关键组成部分。需求文档...

    车辆管理系统(struts+hibernate+spring+oracle)130225.zip

    5. 整合框架:将Struts、Hibernate和Spring整合在一起,可以实现松耦合和高内聚的设计,提高系统的灵活性和可维护性。Spring可以管理和协调其他两个框架,使得业务逻辑和数据访问更加独立,同时也降低了系统的复杂性...

    会员管理系统(struts+hibernate+spring).zip

    Struts处理用户交互,Hibernate负责数据存储,Spring则协调整个系统的运行,形成了一种松耦合、高内聚的架构。这样的设计不仅提高了开发效率,还降低了系统维护和扩展的成本。 在实际开发中,会员管理系统可能包括...

    Spring+struts+hibernate 的 SSH教程

    SSH框架组合使用,可以实现松耦合、高内聚的代码结构,提高开发效率,便于团队协作和后期维护。同时,SSH提供了一套完整的解决方案,覆盖了从用户请求到数据持久化的整个流程。 总结,SSH教程是一个为初学者准备的...

    论坛系统(Struts 2+Hibernate+Spring实现)

    此外,SSH框架的集成使得论坛系统具备了良好的分层架构,Struts 2 在表现层提供视图控制,Hibernate 负责数据层的存储与检索,而Spring则在业务层协调各个组件的工作,形成了一种松耦合、高内聚的设计。这样的架构...

Global site tag (gtag.js) - Google Analytics