由一个简单的OOP的例子所想到的

记得以前在看关于讲面向对象的书的时候都会拿Dog和Cat来做例子。比如他们都会叫,则他们都应该有个Bark方法,然后进一步抽象到Animal这个类。

然后进一步往深处讲,则会跟你谈到设计类的时候应该要关注这个类的行为,其实也就是方法了。同时它具有哪些对我们有用的字段和属性,这里我们不谈这个。

但是现在在实际的操作过程中呢?我感觉很多人都是拿了一个问题之后立即开始分析里面所包含的实体类,这个实体类有哪些字段和属性,完了以后加上CRUD方法,最后再在Business Layer里面根据界面的需求进行一通拼装。OK,Mission Complete!

所以在经过一段时间之后我就迷糊了。比如说在一个人力资源管理系统中,有一个人力资源部用户类HRUser,这个用户可以添加一条新雇员的信息,那么我想这个AddNewEmp的方法应该放在HRUser里面,因为是HRUser来做这件事的。但是实际做项目当中我感觉好像HRUser应该是一个实体类,里面不会给任何的方法,这个AddNewEmp的方法肯定会被写在别的地方,它的存在只是为了去响应用户在UI上点了添加雇员的这个按钮罢了。

中午也因为这个问题跟朋友讨论了一下,他说他喜欢实体类然后加控制器的这种做法。比如说你对Emp这个雇员类当作实体类的话,那么就应该有个EmpController,负责Emp的CRUD这些事情。这样做的好处在于分的很清楚。这样做确实是可以,但是如果这样那么好像我开头所讲的那种分析就没什么意义了,一切东西都能够做成一个实体类加控制器嘛。那么到底是书上讲的压根在实际工作中就行不通呢还是我们的做法根本就不是OOP呢?

到底应该怎样做呢?谈谈你的看法吧。

posted @ 2006-11-26 00:07 阅读(8947) 评论(20) 编辑 收藏

 回复 引用 查看   
#1楼2006-11-26 00:57 | 木野狐      
这两种做法都有,你说的没有行为只有数据的实体类其实叫做 DataObject, 加上了行为后的才是一个规划良好的对象。不过就你的问题而言,我觉得把创建用户的动作加到 HRUser 里面确实不太好,如果是我会做一个 UserManager 来负责做这个事情,然后 HRUser 继承自 User 基类,至于是不是 HRUser 来做这个事情,完全可以在控制中加进去权限判断来做。
在 javaeye 上看过关于几种模型的讨论,好像形容为“贫血模型”,“失血模型”和“充血模型”,但是结果也莫衷一是,总之我觉得这种模型的设计问题需要具体项目具体考虑把,没有一个定法。只要是能够快速开发,并且不失一定的扩展性的,就是好的设计。

 回复 引用 查看   
#2楼2006-11-26 12:43 | 风云      
@木野狐
同意狐兄的观点,“模型的设计问题需要具体项目具体考虑”,如果是一个相对简单的逻辑,可以采用活动记录模式,如果是在一个大型项目中并且业务复杂,那么最好选用 领域模型+ 数据映射器 + 实体对象。

楼主所说的实体类+ 控制器的模式有一定的二义性,该控制器具体是领域逻辑的业务对象还是表现层的MVC中的控制器,当然楼主的说法应该是领域逻辑的,但是传统的观点认为控制器是表现层的概念术语,你把这个概念放到这里有点不是太恰当的!

 回复 引用   
#3楼2006-11-26 15:09 | 卢彦[未注册用户]
没有任何方法的实体对象Martin Fowler称之为贫血对象,早就抨击过了。
采用这种所谓“实体”+“控制器”的设计不要以为是面向对象,其实就是非常典型的事务脚本,面向过程的设计。根本不需要做真正的面向对象分析设计,分析出来了也只是牛头对马嘴,完全套不上。
对小项目来说,无所谓,说不定还更快。如果做大项目,这种方式最后很容易陷入泥沼。

 回复 引用 查看   
#5楼2006-11-26 16:36 | Zhongkeruanjian      
那是因为数据库的概念已纪在你心中根深蒂固了,想想如果没有持久的影响。你会有AddNewEmp这个方法吗?没有!因为在内存里我们创建对象都是NEW。所以AddNewEmp是一个典型的持久功能的方法。根据类的职责单一的原则。我是不主张把持久职责加在一个实体类上的。
 回复 引用   
#6楼2006-11-27 00:00 | pp1982[未注册用户]
纯粹从OO的角度来看,数据应该和其行为绑定在一起,也就是应该是一个“充血模型”。但是从实际开发过程来看,完全的OO又是不现实的。
 回复 引用 查看   
#7楼2006-11-27 11:45 | Klesh Wong      
@卢彦
但是现实中的开发中,如果些CRUD这些行为都加到实体对象身上的话,那么,牵涉到多表关系,数据筛选等操作的时候要怎么处理?这些问题都是我们做开发所要面对的日常问题。实体行为完全可以是其它无关于CRUD的行为,而在数据层面采用“实体”+“控制器”的模型不是可以更好的解决“关系型数据库”与“面向对象”之间的冲突吗?

 回复 引用 查看   
#8楼2006-11-27 12:42 | Icebird      
AddNewEmp不应该算持久的功能,HRUser所需作的仅仅是将NewEmp的所有资料登记并修改相应的工资表,通讯簿等等,而这些行为是一次性的。也就是说把一个局外人与公司的职员表建立起固定的联系。

唯一需要考虑的是HRUser只有登记的功能,而没有决定登记某人的权力。决定录用某人为公司员工应该是公司的管理者的事情,因此,如果方法名称改为AddNewEmpInfo的话,似乎就不会那么令人困惑了

 回复 引用 查看   
#9楼2006-11-27 13:47 | ∈鱼杆      
“采用这种所谓“实体”+“控制器”的设计”我们目前采用的也是这种。实体对象设置层Model层,我们目前在程序中一般设置成 UI层,BLL层,DAL层通用的层。用来当参数对象。也就是重构中的将参数变成对象。如果我现在不用贫血的实体,将这些实体合并到BLL层上,成为有行为的类,那我不知道如何传输数据?目前 BLL调用DAL中一个AddNew(实体)方法,那么不是要更改成这样AddNew(string name,int age,..... )。不用贫血实体的矛盾。
 回复 引用 查看   
#10楼2006-11-27 14:10 | woodhead      
脱离应用场景去讨论这个问题没有多少意义。
对象有其客观存在模型,但基于时间成本和性能成本考量,实践中不可能把整个模型完整的搬运到计算机系统中。 只能寻找一个适当的角度,选取一个适当的层次实现对象模型。
在大规模系统中,由于性能和系统结构等因素,被广泛使用到的业务对象常常采用贫血模型实现,这不算违反OOP,不过是选择了一个较浅的层次,以数据为主的角度对对象进行抽象的结果。而与贫血模型常常同时出现的负责CRUD的各类Helper类,则可以视为从另一个角度对业务对象进行抽象的结果。
对同一个业务对象,从不同角度分别抽象为不同的业务类是很常用的设计手段,这样能够让设计者比较容易在性能和可维护性间取得一个平衡点。当然越多的抽象意味着越多的复杂性,在一个单进程的小型系统中,充血模型往往是最佳的选择。

 回复 引用 查看   
#11楼2006-11-28 09:40 | 小陆      
很多人喜欢用性能来说事,以证明贫血模型有存在的价值。实际上,无论是完整的Emplyee,还是Emplyee+EmplyeeManager, 编译成机器代码其实都差不多,对性能的影响没有很大差别。
比如c++,oo和不oo的性能差别微乎其微,class编译过后就是结构体加过程,唯一的差别就是虚函数调用需要多寻址一次。
我是从来没有见过一个项目因为业务层做的太完整而效率变慢,倒是见过很多quick&dirty的系统经过两年的维护效率像跳水一样下降。

 回复 引用 查看   
#12楼2006-11-28 13:02 | woodhead      
@小陆
只考虑单机的情况自然是这样,问题是在分布式计算中,情况还是这样吗?
在目前的一般应用场景下,编译指令集的效率问题都是忽略不计的,谈到效率时,更多的时候是在说如何将业务逻辑比较优雅地分布到系统内的各台服务器上,以达到更好的相应速度和系统容量的问题。充血模型在这方面确实很难满足要求。
OOP是必须遵循的,但怎么遵循就是很有技巧的事了。 说失血模型不是OO,实在不敢苟同。

 回复 引用 查看   
#13楼2006-11-28 16:04 | 小陆      
失血模型是oo,但是他不是一种完整的业务模型,对维护的帮助也是有限的。
业务模型的基础是business,不是系统的物理架构(系统这时候还没生出来呢),在设计业务层的时候,最初是不必考虑物理结构的,只要符合business的实际地理分布就可以了。
为了实现分布式,可以有两种选择:
1:采用分布式的对象模型,这是最面向对象的一种方式,但是对效率确实有很大的影响。平时人们说oop影响系统效率,其实就是说这样的方式。实际上这样的架构很少有人采用,我是没有看到过一例。
2:退而求其次,将系统分割为多个部分(b/s、c/s。。。),每个部分按照各自的需要实现自己的业务对象,每个部分仍然应该遵守统一的设计准则。这样的实现方式对系统效率不会有影响。

 回复 引用 查看   
#14楼2006-11-29 10:05 | woodhead      
@小陆
呵呵,我也没说失血模型是业务的全部,它确实只是业务模型的一部分。其实无论失血模型还是充血模型设计出的对象在实践中一般都不是业务的全部。
将系统划分为子系统是惯用的做法,你说的“遵守统一的设计准则”重点也就是基于同一套业务模型,每个子系统从自己的角度出发选择模型的一个部分独立实现为一套具体的业务类。这种方式不能说是违背OOP的,因为这种方式也是基于用OOP分析的业务对象模型,并且在实现时也使用OOP。如果非要说“分布式对象+充血模型”才是面向对象的分布式系统,那么也太过于教条主义了。
也就是前面说的这种实现方式,使得失血模型显得必要且重要。因为往往就是使用失血模型建立的对象在系统中承担着连通各个子系统的重任。

 回复 引用   
#15楼2006-11-30 23:44 | 卢彦[未注册用户]
贫血对象不是业务对象,只是DTO
很多系统号称什么面向对象的设计,其实只有DTO对象。业务逻辑都写在SQL里面,根本不是面向对象的业务建模,而是纯粹的面向过程逻辑。
这种方式的设计,只是利用了面向对象来传输数据,完全没有利用到面向对象的分析建模的好处。

 回复 引用   
#16楼2006-12-01 09:21 | RichardLin[未注册用户]
在这个例子中,HRUser其实只是一个Actor而已,是属于系统范围外的,所以根本没必要考虑是不是在HRUser上是否有AddNewEmp这个方法。

把用例图一画就清晰了,HRUser是Actor,用例是AddNewEmp。

 回复 引用 查看   
#17楼[楼主]2006-12-01 09:47 |       
@RichardLin
我举的这个例子只是想说明下问题,并不是我在做这个系统的时候发现的问题 :)

写这篇东西其实是因为手头上一些东西就是用那种失血模型来做成的,做完了以后发现很难去扩展新的功能和复用,总是觉得我们好像根本就没有体会到OOP给我们带来的诸多好处。

 回复 引用 查看   
#18楼2006-12-01 17:40 | henry      
@卢彦
业务逻辑的设计和SQL有什么关系,当业务逻辑处理数据库时执行SQL是必然的;我不见得在业务逻辑里编写SQL就代表业务逻辑设计不OO!
从另一个角度来讲把实体作为业务逻辑的数据输和输出载体,这并不影响业务逻辑的对象性。真搞不懂把原业务逻辑的多个成员集成到一个结构上会导致不能面向对象的分析建模。

 回复 引用   
#19楼2006-12-01 21:19 | 卢彦[未注册用户]
关系大了。
一个是用对象模型来实现业务逻辑,一个是用SQL语句。本质上的区别。

 回复 引用   
#20楼2008-03-20 23:10 | halfmile[未注册用户]
1. 所谈的是OOD(esign)而不是OOP(rogramming)

2. OOD的目的不仅仅在实现,而更重要的在于修订和维护。

3. 从HRUser开始,貌似在谈MVC模式的应用,如果是这样的话,HRUser是一个用来负责其状态的“实体”,为CRUD提供一个抽象的面对对象接口。这个实体和用户输入之间的交互是由xxxController来负责。这样做的最终目的是为了减少紧耦合从而降低维护成本。