如何在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的时候不要直接去使用hibernatespringhibernate的支持。

现在我们在编写 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的时候不要直接使用springspring的继承类

前面我说了应当避免 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.)”过去我们用需求说明书来抑制用户的变更,现在发现不能这样了。敏 捷软件开发提出了许多应对用户变更的办法,其中建立低耦合高内聚的软件结构也是办法之一。系统中的所有对象都有自己的明确职责,这个职责应当不多且高度相 关。每个对象都应当只完成自己的职责,而把其它的任务交给别人去做。正如我前面提到的例子,评审表对象只完成与评审表相关的操作,而在它需要完成的任务 中,需要使用评审计划数据的相关功能,交给评审计划对象去完成,评审表只管调用。这样的构造要求开发者相互协调,彼此多交流,同时,也需要有人来统一规 划,站在全局的设计这个系统。通过这些,我们才可以适应变化,提高设计水平。

相关链接:回复:《如何在struts+spring+hibernate的框架下构建低耦合高内聚的软件
  • Caeb8220-1b1e-4efe-93d2-eda8a63db6fc-thumb
  • 大小: 40.7 KB
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值