锁定老帖子 主题:关于小mis设计的一些经验
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2004-12-07
大道至简 我觉得程序设计的唯一需要遵守的规则就是“尽力保持系统简单”,不管是我们反对goto,提倡模块化编程,引入分层结构,支持面向对象都是为了同一个目的。简单的系统才能具有良好的可读性和可维护性。因此我坚信这个原则才是我们设计的最高原则。和这个原则冲突的我们就认为可能是有错误的,需要慎重考虑的。 DRY,Don't repeat youself. 这个常常是检验程序是否简单的一个方法。 这个世界上有许多有价值的,需要我们学习的东西,面向对象仅仅是其中的一部分。面向对象能解决我们的很多问题,但绝对不是全部。 javascipt, store procedure都是有用东西,善用它可以简化我们的系统,不要试图构造单纯的java服务器程序,把所有的问题都在java服务器内解决只能得到可怕的低效的系统。 下面的描述,是通过我在一些小MIS项目上的经验得来的。小项目是指开发成本在20-30个人月之间,实施成本在10-20人月之间的项目,只有浏览器客户端。我相信这种规模的项目是很多的。在这样的上下文中讨论问题,是因为我做了4个这种规模的项目。 1 Open Close Principle 为扩展open, 为变化close 这个原则被我们看得很重,为实现这个目标,大量的引入了interface和factory. 甚至于好像所有的东西都只能依赖于interface. 可是是不是所有的东西都会变化呢?是不是某一个类所依赖的对象都可能会发生变化呢? 引入这些东西都会增加系统的复杂度,对于不了解的东西,不妨先简单的对待,在需要变化的时候在引入interface,factory是不是会更好呢? 因此,我觉得应该重新理解open close: 为了可能的扩展而open, 为了变化close. 虽然我看过的书上都写着要预估那些地方需要extension, 但是好像很少在帖子里面看到。 事实上在业务系统中存在住很多的记录性业务(仅仅增删改查就可以了),而且和系统中其他的部分没有什么关联。这种业务用你熟悉的最简单的方式对待就好了,quick and dirty的做法就足够了。 归根到底,open close 只是简单性原则在如何应变上的一种做法。 2 DAO 目的:隔离业务层和data source, 使两个层次可以独立的变化 dao最初的出现时代还没有ormapping, 使用dao可以把复杂的jdbc调用分离于业务代码之外,还可以方便的移植到其他数据库上。 ormapping 到来以后dao原来的用途之一隔离复杂的jdbc就失去意义了,而且ormapping工具一般也支持不同的数据库平台。 还剩下的就是独立变化(不依赖于特定的ormapping工具)。我想说的是:hibernate真的是那么没有入侵性吗,我们写出的代码的模式真的没有受到hibernate的影响吗? 一个成功的mis项目响应时间是很重要的,这是决定项目成败的关键因素之一。而一般情况下如何更合理的使用hql和sql是优化的关键部分,在这样的情况下,hibernate真的没有干扰我们的思路吗? 在此,我提一个例子,为了使用cache, 我们常常要面向iterator进行编程,我觉得这就是一种入侵性。实际上,很可能存在一种ormming工具实现自己的List类,同样完成在访问时进行加载的工作。如果,你首先选择这种工具并且广泛的使用List,当你在转换到hibernate上的时候就有一点麻烦(就算你自己来写一个list类封装住iterator,也要自己区分到底什么时候该list, 什么时候该find, 因为涉及到group by的问题)。 在我讨论的项目规模里面,替换hibernate的可能性很小, 这里也想请有过通过dao隔离来进行ormapping工具转换的朋友谈谈自己的经验,我也很想知道这种工作到底有什么样的难度,dao是不是真的能行得通? 还想讨论的就是dao中应该包含那些方法。一般就是增删改查,关键就在于查,查询是多变的,客户常常会想增加一些条件。而且很多的查询常常就是去动态拼接一个hql。查询的变化大,而且没有常常没有复用的可能。当然,有一些人推荐使用传递数组或是Collection类型的参数来处理这样的情况,可是,我觉得这样的程序一点也不直观,需要看着dao的定义来写业务层,参数的次序和个数都很模糊。 综合上述的陈述,我们是否可以认为query是不应该被封装的呢?可是如果query不需要封装,dao 就蜕变成了一个HibernateUtil,也就不能称之为dao了。 还有一个问题就是有些业务需求本身能转化成一个有点长的hql, 这种情况下,这个东西该放在哪里呢?在综合查询模块里面这样例子很多! 我觉得dao的问题很多,因此,一般我是反对在项目里面使用dao的 3 Test 测试的基本观点就是测试可能出问题的地方。 hql对程序的入侵性,使得我们需要在实际的环境中去做测试,因为hql的正确性也是我们要测试的一个东西。而且了性能我们也会直接使用update语句来进行计算,这样我们就更需要在实际环境中进行测试了。 我在测试这种东西的时候,一般先做一个begin, 测试的过程中使用若干次flush, 测试结束以后,无论成功失败都要rollback回去 4 分层 分层应该适度,在我讨论的上下文里面,我觉得vo, to 都是没有必要普遍存在的,po应该是贯穿始终的。 action应该承担更多的任务,普通的增加、删除和保存,查找等非核心业务,我认为是可以放在这里的。 既然这些非核心性业务所需参数是多变的,而且参数数量又是巨大的,传递起来是个麻烦,不如干脆就不传递了,直接在action里面处理就好了。 通过hibernate的能力和一些code generation技术,我们可以把这些东西变成不需要测试的。 因此,我们的目光集中在核心业务上就够了。把易变动的非核心性业务弄得简单一些吧。 项目中核心性业务一般是稳定的,我们的目光集中在这里就好了。 5 View 在一般情况下使用po作为介质就够了,引入vo之类的东西,不仅重复而且在出现分歧时干扰大家的思路。 分页:一般情况下,我是反对分页的,分页的显示不利于操作者工作,不断的点击上一页下一页是很麻烦的,更好的方式是提供丰富的查询条件,使操作者能快速的找到自己要找的东西。 list or iteraotr : 在hibernate中,使用iterator才能进行cache, 而且,整体上使用iterator可以减少系统的响应时间和内存颠簸 同时结合iterator广泛地使用流计算模型,可以简化很多计算合计类型的工作。 6 Validate 好像有很多的框架都把validate当作一个很重要的东西,实现了不少东西。但是,我在实践中觉得validate的用处很有限,页面和框架中的数据类型的验证,以及确认不可为空的验证是很有的,其他的验证也许有用,不过我没有用到过。因为验证往往是业务逻辑紧密结合在一起的,通用的验证方式带来的方便太少了。 服务器端的验证,有人喜欢使用独立的验证方法,或者在直接在框架中调用业务类的验证方法。总之,这种倾向是把验证方法独立出来。我不太喜欢这种方法,因为为了验证常常要仿照真正的业务方法去做处理,看起来结构上和真的业务方法一样,我觉得这是一种重复。而且当业务变化的时候,还要同时修改两个地方很是麻烦。因此,干脆就不写独立的验证方法了,直接在业务方法中进行检测抛出异常就好了。 7 核心业务的设计 再次强调一下,下面的描述是根据小mis系统的特点来进行的,绝对不是普遍的设计方法。 核心业务的模式,从思路上看,我觉得大体上有两种, 一种是面向业务功能的,老式的cgi系统常常是这个类型。每一个业务入口点自己完成全部的工作,每个入口点基本上都是无关的,相互之间无依赖性,我相信这样这种方式还是有很多人在使用的。 还有一种是面向对象的,通过职责分开,使得每一个业务功能的实现都是由若干个相关对象共同完成的。每个对象拥有某些职责,大家协同工作,一起把业务完成好。 从传统的oo的观点看,oo就是数据和操作的结合,代表着一个有一定内涵(数据)和职责的实体,这是oo最初的哲学理念。发展到今天已经变化的很大了,人们好像更多的将封装,隔离,多态作为oo的主要特征。同时结合了分层,来设计系统。 但是不管怎么样,如果,每个业务入口点在一个方法里面控制全部的相关po来完成工作,是不能称之为面向对象的。即使使用了spring, dao, hibernate,我也只是认为你做了分层,而没有做面向对象。使用c做面向对象是需要一点技巧的,使用java做面向过程却是很简单的。(在我的两年半的java生涯里面,至少有2年是在面向过程) 在使用单独的业务层和po层情况下,如果,面向对象分开职责,就会产生 一个po 对应一个业务类的情况(当然有些po没有对应的业务类, 严格的说一个po对应0..1个业务类),从而产生了两个独立的类体系,一个是业务的,一个是属性的(po).因为po的设计一般是和业务紧密联系在一起的,因此,po和业务类之间常常有对应的关系。因此在mis系统中po和业务类常常也有关系的。 这样就引发了我建立统一的类结构的想法,po一般都会对应着业务实体,将业务方法和属性合一,建立一个简单的继承关系。 abstract _Contract 里面充满了getter, setter Contact extends _Contract { ... } 除了在Contract里面,任何其他的地方都只有Contract而对_Contract一无所知 将业务方法放置于Contract中。 同时通过使用strategy模式来达到业务逻辑的灵活性,在简单性原则下,我仅会在需要的时候才引入strategy模式。由于业务系统的特点,一般除了综合查询以外基本不会需要访问和自己不相关的po,因此一般而言和其他的类相关联较少。 以上是我在mis系统设计中的一些看法,和类似于Spring的结构相比,缺少灵活性,也不够漂亮, 但是在小mis系统我认为是足够用的,过多的interface和隔离会把系统弄得过于复杂。其中的得失我也还在反复思考。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2004-12-07
读了上文,特别有感触. 希望能够展开讨论.
也想说说DAO, 想以一个尝试去理解DAO设计初衷的角度去写一些反对DAO的话. 如果设计了DAO,会是一个什么效果: 在自己用jdbc实现persistence层时, 我想大多数的情况下是一个数据模型建一个对应的xxPersistence, 它除了对这个模型所对应的表做增删 改之外, 麻烦的就是查询了, 而这要考虑到这个模型的设计中是否包含, 比如一个人员所属一个部门这个模型,常见的可能有两种设计形态: 1 Staff { int obj_id, string code, name, ....... int sex, age, ......... int department_id } 2 Staff { int obj_id, string code, name, ....... int sex, age, ......... Department dept } 那么在查询的时候, 根据这两种形态的不同, 还要涉及到对其他表(Department)的查询. 当然如果考虑了cache的话, 可能在具体的查询步骤上 有不一样,比如查department时先把obj_id查出来, 然后再在cache里找, 然后再到数据库. 不管怎么样,我想大多数的自己用jdbc实现的persistence-layer, 大体都是这种形式的. 在如何能够 把数据模型和操作数据模型与库关系 的这两部分很好的区分开 这一点上做的不够. 引申一下, 我想沿着这个模型走的同事们, 编到一定程度后都会感觉到这个问题. 然后考虑如何能够把 映射 独立的做出来, 我想肯定 有成功实现的, 但也可能在做一个比较好的 persistence层父类 后, 就力不从心了. o-r mapping, 我想上面提到的 - 严格来说是要和 o, r绑在一起才能达到它的作用. o,-,r 这三者是分不开的. 我的理解是 o: object, 数据模型; r: relation, 数据源(库); -: 映射, persistence层. 所以 mapping 模块能够独立出来就是目标了 现在, 发展到了jdo, hibernate的时代. 以hibernate为例, 我想它实现了我所期望的, 而且使用了hibernate后, 能够使一些设计上的问题 不再成为问题, 它具有使大部分的应用出来后能够有一个大致相同结构的这种效果. 比如上面提到的Staff设计的问题, 我想1那种情况, 就很 少会用到了. 而在这个时候如果还再去设计一个DAO层的话, 我们如何给它定位呢. 还有就是DAO的数量, 我想做到后来, 恐怕也会不少. 会和原来用xxPersistence时一样多. 所以我想说, DAO替换了原来的Persistence, 我们实际上在用原来老的思想, 是原来设计persistence时有感情,不想轻易舍弃呢, 还是思维的惯性呢. 我想大多数是自觉不自觉的就设计 了这一层. 出来以后, 我想恐怕效果就是把 HibernateUtil这种地位的类 按照不同的模型去分, 然后把在HibernateUtil 里方法又都写了一遍. 可能有save啦, 或者找根据id找一个模型啦等等. 可能还有在DAO控制事务的, 我想这就更不可取了. 当然上面提到的那些find方法可能不容易放到hibernateutil去做. 关于上文中提到的核心业务及其设计方法, 很想听听更多意见. |
|
返回顶楼 | |
发表时间:2004-12-07
最近刚好在看Domain Logic方面的东西,在Martin Fowler的《Patterns of Enterprise Application Architecture》有比较详细的讨论。
引用 在使用单独的业务层和po层情况下,如果,面向对象分开职责,就会产生 一个po 对应一个业务类的情况(当然有些po没有对应的业务类, 严格的说一个po对应0..1个业务类),从而产生了两个独立的类体系,一个是业务的,一个是属性的(po).因为po的设计一般是和业务紧密联系在一起的,因此,po和业务类之间常常有对应的关系。因此在mis系统中po和业务类常常也有关系的。
就是书中说的贫血的领域模型,martin fowler不太推荐这个。 引用 样就引发了我建立统一的类结构的想法,po一般都会对应着业务实体,将业务方法和属性合一,建立一个简单的继承关系。
abstract _Contract 里面充满了getter, setter Contact extends _Contract { ... } 除了在Contract里面,任何其他的地方都只有Contract而对_Contract一无所知 将业务方法放置于Contract中。 我的疑问是为什么要用继承呢?直接在PO上加上业务方法使之成为BO不是更加简单?而且我没有感觉到用继承会带来多大的好处。 我在这方面的经验太少,只能给个看到的好文章的连接了:http://starrynight.blogdriver.com/starrynight/172060.html |
|
返回顶楼 | |
发表时间:2004-12-07
引用 在此,我提一个例子,为了使用cache, 我们常常要面向iterator进行编程,我觉得这就是一种入侵性。
但是cache不应该在那里做。在那里做cache的话,虽然不做查询,但仍然会开session和事务,开销仍然很大。cache应该在business facade的外面做,这样一来逻辑清晰,二来完全避免了不必要的开关数据库,三来完全不受实现内部细节的影响。 |
|
返回顶楼 | |
发表时间:2004-12-07
gigix 写道 引用 在此,我提一个例子,为了使用cache, 我们常常要面向iterator进行编程,我觉得这就是一种入侵性。
但是cache不应该在那里做。在那里做cache的话,虽然不做查询,但仍然会开session和事务,开销仍然很大。cache应该在business facade的外面做,这样一来逻辑清晰,二来完全避免了不必要的开关数据库,三来完全不受实现内部细节的影响。 我指的就是在做查询的时候 |
|
返回顶楼 | |
发表时间:2004-12-07
de3light 写道 最近刚好在看Domain Logic方面的东西,在Martin Fowler的《Patterns of Enterprise Application Architecture》有比较详细的讨论。
引用 在使用单独的业务层和po层情况下,如果,面向对象分开职责,就会产生 一个po 对应一个业务类的情况(当然有些po没有对应的业务类, 严格的说一个po对应0..1个业务类),从而产生了两个独立的类体系,一个是业务的,一个是属性的(po).因为po的设计一般是和业务紧密联系在一起的,因此,po和业务类之间常常有对应的关系。因此在mis系统中po和业务类常常也有关系的。
就是书中说的贫血的领域模型,martin fowler不太推荐这个。 引用 样就引发了我建立统一的类结构的想法,po一般都会对应着业务实体,将业务方法和属性合一,建立一个简单的继承关系。
abstract _Contract 里面充满了getter, setter Contact extends _Contract { ... } 除了在Contract里面,任何其他的地方都只有Contract而对_Contract一无所知 将业务方法放置于Contract中。 我的疑问是为什么要用继承呢?直接在PO上加上业务方法使之成为BO不是更加简单?而且我没有感觉到用继承会带来多大的好处。 我在这方面的经验太少,只能给个看到的好文章的连接了:http://starrynight.blogdriver.com/starrynight/172060.html 1 继承是因为,po一般是通过code generation产生的,而且分开之后,代码更加清楚 2 我觉得设计应该随着业务的特点而变动,我个人的感觉在小mis里面,po和业务的联系确实是很紧密地 |
|
返回顶楼 | |
发表时间:2004-12-07
引用 1 继承是因为,po一般是通过code generation产生的,而且分开之后,代码更加清楚
code generation:你是先建好数据库的表结构然后再反向生成hibernate的映射文件和POJO么?如果是的化用继承也蛮自然的。 我的做法不太一样,我是先建好POJO,用xdoclet建立映射文件,然后用schema exprot自动生成数据库的。业务方法可随时添加。所以我没有这样的继承关系,就是一个PO或者说BO。 呵呵,做法不同而已。 |
|
返回顶楼 | |
发表时间:2004-12-07
de3light 写道 引用 1 继承是因为,po一般是通过code generation产生的,而且分开之后,代码更加清楚
code generation:你是先建好数据库的表结构然后再反向生成hibernate的映射文件和POJO么?如果是的化用继承也蛮自然的。 我的做法不太一样,我是先建好POJO,用xdoclet建立映射文件,然后用schema exprot自动生成数据库的。业务方法可随时添加。所以我没有这样的继承关系,就是一个PO或者说BO。 呵呵,做法不同而已。 我以前也用过xdoclet, 当时有一个问题让我觉得特别的不爽,如果我在父类里面实现了某个需要映射的东西,但是,映射的字段需要在子类中去指定时,需要我在子类中进行重载,仅仅为了加一个xdoclet标签。 |
|
返回顶楼 | |
发表时间:2004-12-07
gigix 写道 引用 在此,我提一个例子,为了使用cache, 我们常常要面向iterator进行编程,我觉得这就是一种入侵性。
但是cache不应该在那里做。在那里做cache的话,虽然不做查询,但仍然会开session和事务,开销仍然很大。cache应该在business facade的外面做,这样一来逻辑清晰,二来完全避免了不必要的开关数据库,三来完全不受实现内部细节的影响。 一个问题,可能比较菜 ,business facade外面又是什么,是service layer么?怎么在business facade外面做cache? |
|
返回顶楼 | |
发表时间:2004-12-08
de3light 写道 gigix 写道 引用 在此,我提一个例子,为了使用cache, 我们常常要面向iterator进行编程,我觉得这就是一种入侵性。
但是cache不应该在那里做。在那里做cache的话,虽然不做查询,但仍然会开session和事务,开销仍然很大。cache应该在business facade的外面做,这样一来逻辑清晰,二来完全避免了不必要的开关数据库,三来完全不受实现内部细节的影响。 一个问题,可能比较菜 ,business facade外面又是什么,是service layer么?怎么在business facade外面做cache? 在调用BF前查Cache,找到了,返回。找不到,调用BF,把结果放到Cache,返回。 |
|
返回顶楼 | |