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

问题的提出

分析与决策
1.       编写DAO的时候不要直接去使用hibernate或spring对hibernate的支持。
现在我们在编写DAO的时候普遍都是直接继承spring对hibernate的封装类HibernateDaoSupport,然后使用该类提供的诸如等等。另外,在使用方法实现一些更复杂的hibernate功能的时候还会使用hibernate的类,诸如Query, Session, Type等。这样直接使用spring和hibernate的类存在的问题在于,你的代码将不得不依赖与spring和hibernate的某个版本。比如说,现在hibernate3出来了,改动挺大,实际上最要命的是包结构,hibernate2的包结构是,然而hibernate3是。同样,spring为了支持hibernate3,包名也改为。假如,你现在新开发一个项目,这没什么关系,如果是升级一个项目问题就来了。如果你希望将你的一个项目从hibernate2升级为hibernate3,你不得不修改DAO中所有对hibernate和spring-hibernate的引用。如果你的代码中出现hibernate2与hibernate3不兼容的方法和类,比如(在hibernate3中已经没有了),你还将不得不改写。那么你可能会说,我不会这样升级。如果你的软件生命周期有好多年,hibernate升级到4,升级到5,你还是依然使用hibernate2?如果你以这种方式开发一个平台,你能要求所有使用你平台的软件项目都只能使用hibernate2?更进一步说,我现在开发一个产品,今后的客户将是成千上万。经过1、2年我需要升级了,这时我的升级包有几十M,几乎把所有的DAO都换了个遍,这样的升级无异于重装。也许,有人会提出另一个方案,在HibernateDaoSupport与DAO中间增加了一个基础类,这样将基础类中的,改为了,这样其下面继承的DAO就不用改动了。然而在源码上是小小的改动,但对于类来说,两个不同版本的其相关的属性和方法还是有不少变化,那么在基础类重新编译的同时,你的继承类重新编译否。既然已经重新编译了,因此你的所有DAO在升级的时候依然要打入升级包,问题依然存在。
以上问题,究其原因,是我们项目中的DAO依赖于hibernate和spring,因为我们对它们的使用是继承,是一种很强的关联,就是一种依赖。我们只需要稍微进行一些调整,就可以解决这个问题,那就是不使用直接继承,而使用接口进行分离。可以使用Façade模式,先建立一个叫BasicDao的基础类,从名称我们可以看出,它是所有DAO的基础类,实现DAO操作所需的所有诸如、、、等方法,除了一些基本的方法,诸如翻页查询、getCount、解析查询条件形成HQL语句等功能也在这里实现,但是不要使用与hibernate或spring有关的任何方法和类。同时,BasicDao调用一个叫DaoSupport的接口,DaoSupport的接口则是提供持久化所需的基本方法,最原始的元素。然后,我为DaoSupport接口提供各种不同的实现,比如hibernate2的实现DaoSupportHibernateImp、hibernate3的实现DaoSupportHibernate3Imp,整个结构如下图所示。BasicDao可以使用hibernate或spring提供的方法,但是不是直接使用,而是通过调用DaoSupport的实现类来使用。然而BasicDao到底是使用的那个实现类,我们通过spring的IoC,通过配置文件来决定到底使用哪个实现。同时,BasicDao也不要使用诸如SpringContext的类来实现IoC,而是通过建立和方法,然后在spring配置文件中建立引用。
2.       编写Action的时候不要直接使用spring和spring的继承类
前面我说了应当避免DAO引用spring或hibernate及其继承类。同样的事情也发生在Action中。由于Action通常不纳入spring的管理,因此Action在通过spring调用某个BUS的时候,往往是去引用一个叫的类(spring的类的继承类然后使用它的方法。如此的使用,我们的Action将依赖与spring。我们同样可以使用一个叫BasicAction的父类,然后用一个接口来隔离spring。由于Action通常不纳入spring的管理,我们通过一个的配置文件来决定接口到底调用哪个实现类。这样的结构的另一个好处是,我们还可以将所有Action都必须使用的诸如写日志、用户校验、异常处理都放在父类BasicAction中,提高系统的可维护性。
 
3.       当BUS需要获取别的模块的数据的时候,不要直接去使用该模块的DAO
我举一个简单的例子:我需要设计一个软件评审的管理软件,该软件分为评审组织者制订评审计划、评审者分别填写评审表后由评审组织者汇总评审表、评审组织者制作评审报告。这是一个非常简单的项目,分成了三个人来完成。但是项目进行快结束的时候却出现了问题。填写评审表需要获得评审计划中的一些数据,制作评审报告的数据来源于评审表。项目组在开始编程前先开了一次会,大家约定好了各个部分的数据格式及其规则,然后开始工作。然而数天后项目组把各个模块整合以后发现,系统根本跑不起来,为什么呢?设计评审计划的人发现,所有评审计划应当按照产品编号来进行管理而不是项目编号。由于这个变更,填写评审表模块在待评审列表中什么都无法显示;同样,设计评审表的人发现,在一个评审计划中评审表与评审者不是一对多的关系,而是一对一的关系,因而修改了这两个表的关联。因为这样,在制作评审报告时就不能正确得到评审表数据。其实一个软件项目在整个进行过程中总是不断变更。我们需要做的不是去抑制这些变更,而应当是通过软件的结构去适应这些变更,即是降低各模块间的依赖(耦合),提高内聚。
拿这个实例来说,当评审表需要调用评审计划的数据的时候,不应当是自己写一个DAO去调用评审计划的数据,而应当是调用评审计划的接口,将这个任务交给评审计划类来完成。当评审报告需要调用评审表的数据的时候,同样应当去调用评审表的接口,由评审表来实现。同时,这种调用应当是去调用BUS层的接口。为什么呢?比如在评审计划中的一个业务逻辑是只有在评审计划发布以后才能制作评审表,那么怎样才是已发布的评审计划呢?这个业务逻辑应当由谁来定义?当然是评审计划。在什么地方定义?当然是BUS而不是DAO,因为DAO仅仅是实现数据的持久化,而BUS才是实现业务逻辑的地方。既然如此,如果评审表去调用评审计划的DAO,那么已发布评审计划的业务逻辑必然包含在了评审表的业务逻辑里了。我们假设有一天,已发布评审计划的业务逻辑发生变更了(实际上这样的会在你毫不经意间就发生了),编写评审计划的人会很快就修改了评审计划的业务实现并且测试通过了。他不知道评审表里也包含了这样的业务逻辑,因而修改后的程序在运行到评审表的时候就很可能会出错。不幸的是,在实际工作中,同样一个业务逻辑可能包含在无数个你可能知道,但你也可能不知道的代码中。这样的结构就是一个不易于维护的差的结构。
 
总结:从技术升级和需求变更两方面适应变化

相关推荐