论坛首页 Java企业应用论坛

贫血就贫血,咂地?

浏览 54118 次
精华帖 (1) :: 良好帖 (0) :: 新手帖 (1) :: 隐藏帖 (0)
作者 正文
   发表时间:2005-10-30  
ajoo 写道
单独分一个entity层,更象一个权宜之计。就像我前面分析的,持久层为了避免反向依赖,同时又懒得在持久层和业务层之间进行ItemData到Item的转换。所以为了方便,就弄了这么个层。

事实上我觉得这个贫血模型无可厚非.马丁在这种细枝末节上纠缠挺没劲的.而且,还容易误导大众,比如xiecc那个帖子我觉得就是生生被马丁误导了。

其实像firebody说得,领域对象确实不一定非要贫血,但是也不一定一个不包含多少业务逻辑的对象就是错的。还是要具体分析,根据OO的各种原则,看看业务逻辑放在哪里最好。

很不幸,虽然我的观点大概是这个意思。 但是我还是认为:
业务逻辑放在实体层 要 比作一个Service Layer要来得更OO一些。
只不过,这个实施很容易带来设计的问题,在具体的项目中他需要更深厚的OO设计的基础,可能它的实际优势会因为建模者的错误而损失殆尽。
在领域模型不是很复杂的项目中,Service Layer +贫血的Entity layer 与 富含领域的实体层相比,两者之间没有明显的优势。 而且,正如我一直表达的,习惯过程式编码远远要比习惯OO式思维要来得容易得多。  也要有效率的多。 然而,无论如何我还是不喜欢这样的领域模型,不仅因为它看起来很贫血,而且因为它引入了一种惯性的过程式编码的思维。
然而为了达到实体层是真正的领域模型,还要打破一个惯性思维: 让Entity层可以依赖DAO接口。
正如我上面所提到的帖子里的讨论一样,其实里面的内容还是很容易引起混淆的,有时我所说的领域模型是指实体层,而这里的领域模型又是Service Layer+entity Layer . 真实惭愧,看得我都糊涂起来了。
0 请登录后投票
   发表时间:2005-10-30  
引用
Item对象必须通过某种方法通知Service对象


前面定义Service是接口,这里说Service是对象,前后矛盾,所以你后面的依赖分析就有问题,因为可以让Item实现Service.

另外spring将提供的支持可以看看这个
http://forum.springframework.org/showthread.php?t=19098
0 请登录后投票
   发表时间:2005-10-30  
nihongye 写道
引用
Item对象必须通过某种方法通知Service对象


前面定义Service是接口,这里说Service是对象,前后矛盾,所以你后面的依赖分析就有问题,因为可以让Item实现Service.

另外spring将提供的支持可以看看这个
http://forum.springframework.org/showthread.php?t=19098


这有什么矛盾的?我不过是说一个实现Item的对象要和那个产生它的Service类型的对象通信。省略了两个字会造成歧义么?

至于你说Item实现Service。这个思路比较新颖。你是说,一个Item支持findAll(),getItemById()这些动作?好像这些动作都不属于一个Item的呀。而且,在你没有得到一个Item类型对象之前,怎么调用getItemById()来得到第一个Item对象呢?

至于那个spring,一样是用aop和annotation的思路。我一贯比较反感这种怪异的想法,觉得没必要引入这种复杂度。

比较一下我的那个方案:

interface Injector{
  void inject(Object obj);;
}
class AnythingCreatingComputer{
  private final Injector injector;
  private final Persistence pers;
  Computer createComputer();{
    Computer c = pers.readComputer();;
    injector.inject(c);;
    return c;
  }
}


完全不需要fancy的aop。就是一个简单的ioc就搞定了。你觉得怎么样呢?
0 请登录后投票
   发表时间:2005-10-30  
ajoo 写道
nihongye 写道
引用
Item对象必须通过某种方法通知Service对象


前面定义Service是接口,这里说Service是对象,前后矛盾,所以你后面的依赖分析就有问题,因为可以让Item实现Service.

另外spring将提供的支持可以看看这个
http://forum.springframework.org/showthread.php?t=19098


这有什么矛盾的?我不过是说一个实现Item的对象要和那个产生它的Service类型的对象通信。省略了两个字会造成歧义么?

至于你说Item实现Service。这个思路比较新颖。你是说,一个Item支持findAll(),getItemById()这些动作?好像这些动作都不属于一个Item的呀。而且,在你没有得到一个Item类型对象之前,怎么调用getItemById()来得到第一个Item对象呢?

至于那个spring,一样是用aop和annotation的思路。我一贯比较反感这种怪异的想法,觉得没必要引入这种复杂度。

比较一下我的那个方案:

interface Injector{
  void inject(Object obj);;
}
class AnythingCreatingComputer{
  private final Injector injector;
  private final Persistence pers;
  Computer createComputer();{
    Computer c = pers.readComputer();;
    injector.inject(c);;
    return c;
  }
}


完全不需要fancy的aop。就是一个简单的ioc就搞定了。你觉得怎么样呢?

一个Item支持findAll(),getItemById()这些动作?好像这些动作都不属于一个Item的呀。而且,在你没有得到一个Item类型对象之前,怎么调用getItemById()来得到第一个Item对象呢?

这些Query我一般把它们放到DAO接口,或者抽取出来作为一个CommonQuery 接口。 但是,Save ,update这些接口是可以放在Item上的。
0 请登录后投票
   发表时间:2005-10-30  
引用
你是说,一个Item支持findAll(),getItemById()这些动作?具体怎么选择,我想更多的取决于你的rich domain object到底有多rich

那么我让领域对象很rich,连Service也不要,做如下关于领域对象职责的设想:
1.某对象类型向外部提供访问本类型对象实例信息接口,提供创建对象实例的接口。
2.对象实例自我销毁、更新状态.
3.对象实例只与领域对象(符合这四个假设的)交互。
4.领域对象的方法也具备事务性。
从这些设想上看,似乎看不出来有什么问题?
0 请登录后投票
   发表时间:2005-10-30  
nihongye 写道
引用
你是说,一个Item支持findAll(),getItemById()这些动作?具体怎么选择,我想更多的取决于你的rich domain object到底有多rich

那么我让领域对象很rich,连Service也不要,做如下关于领域对象职责的设想:
1.某对象类型向外部提供访问本类型对象实例信息接口,提供创建对象实例的接口。
2.对象实例自我销毁、更新状态.
3.对象实例只与领域对象(符合这四个假设的)交互。
4.领域对象的方法也具备事务性。
从这些设想上看,似乎看不出来有什么问题?

4.领域对象的方法也具备事务性。

我想在具体应用中最好还是在外面包一层Service Facade ,事务可以定义在这一层,只不过这一Service Facade仅仅是很薄的一层 。几乎所有的逻辑都应该由领域模型来完成。而Service Facade是处理诸如数据解析/前台处理之类的较复杂的逻辑的。

我想,领域模型应该着重于业务逻辑的部分, 而在具体的应用中,可以外包一层很薄的Service Facade,这一个Service Facade是特定于 特定的应用的,假设一个系统可能有两种发布方式:
1  )发布在Web 方式下,数据传输交互以Http Form 形式

2)  或者发布在SWing 客户端界面下 ,数据传输以特定XML 方式。

1 )
方式下,可以将DTO /Form Bean (假设不是OGNL的话)直接传递到Service Facade ,这种Service Facade是特定于Web Form/Struts 方式的

2)可以将XML String直接传递到 特定的Service Facade (或者更好的方式是用一个XML 报文对象来代替) ,此时Service Facade是特定于这种XML报文传输方式的。

此时的设计,把事务定义在Service Facade上是合乎逻辑的。

然而,如果把事务定义在实体上的方法上,不是很合乎重用原则,至少,事务部应该定义在这一层上。
0 请登录后投票
   发表时间:2005-10-31  
引用
这些Query我一般把它们放到DAO接口,或者抽取出来作为一个CommonQuery 接口。 但是,Save ,update这些接口是可以放在Item上的。

我说的就是这个设计。

因为抛开持久层与否的问题,单纯从语义上说,item.save()必然会影响CommonQuery.findAll()函数的结果,对么?你通过item.save()增加了一个item对象,必须要从findAll()函数反应出来。这就是说,语义上,save()和findAll()必然是有耦合的。你可以试着直接在内存里面用collection来做save(),也一样逃不了这个耦合。
你或者让Item依赖CommonQuery,或者,让两者都有一个共同的约定(比如都操作某个数据库的某张表,某个全局的collection对象之类的)

设计两个接口,让它们互相之间有天然的耦合,感觉并不好。没有达到do one thing and one thing only的要求。虽然item.save()比service.save(item)看着爽。

我说service好,是因为它从语义角度把相关的动作组合在了一起,即使语法上看起来不那么漂亮。

当然,我不是说item.save()就绝对不好,象马丁那样武断地就搞出个anti-pattern来。

完全强求一个entity layer和service layer也不见得就好。我就见过一个项目,设计者在每层都搞了对应的贫血对象,然后在各层之间穿梭的时候要频繁在贫血对象间转换,恶心地一塌糊涂。

设计就是个权衡,有时候耦合也不是什么天大的事,同样,贫血模型也不见得就是天大的事。

只不过这些方法各有优缺点,完全应该根据具体情况选择分析,我反对马丁那样不分青红皂白上来一棍子把贫血模型打倒。

说实话,我倒觉得强求rich,甚至造成xiecc那个例子里面很笨拙地注射依赖,造成了一种“充血模型”—— hyperaemia domain model。
0 请登录后投票
   发表时间:2005-10-31  
再强调一遍我的观点,兼回答无明的pm。(好像pm系统出毛病了,发不了)

我认为,分析domain object得设计,就需要忘掉持久层这码事。不同层次之间有不同的抽象。只要在业务层分析对象关系就可以了。
或者,可以假设对象就存在内存中的某个list中,而不是什么数据库。如此,可以简化分析,抓住本质。也不会因为明天出了某个神奇的orm可以任意持久化任何java对象而改变结论。

不是说谁负责持久化的问题。而是说item.save()和manager.save(item)哪个更符合面向对象的松耦合,强内聚的要求。

这样看,item.save()和manager.queryById()两个语义上存在耦合的动作分开在Item和Manager中并不合适。因为如果分开,Item就必然耦合于Manager,而称为不了一个独立的实体。
0 请登录后投票
   发表时间:2005-10-31  
hostler 写道

贫血不贫血这个问题也无所谓,Matin同志酒精考验,火眼精精,哪能看不到双向依赖的问题,此问题被Matin同志用了一招Separated Interface进行化解

哪里有这个介绍?给了link。martin不是神仙,我不相信他真的能化腐朽为神奇。更可能的是,他只是转移了依赖。或者,象我说的那样,通过一个转化,在业务层把纯数据的ItemData转换为rich model的Item。


hostler 写道

Martin 同志立论依据倒也非
引用
分离数据和行为的方法不是OO,而是面向过程,所以该打倒!
这么简单,POEAA就有那个“收入确认”体现贫富差距的例子,ajoo不妨研究研究。

Robbin说道用Hibernate做rich Model有些困难,其实Hibernate有他做的不到位的地方,需另开贴讨论了

你仔细读读martin的原文吧。除了“这不OO”,我还真没看见他给出什么有力的论据。
0 请登录后投票
   发表时间:2005-10-31  
说实话,我倒觉得强求rich,甚至造成xiecc那个例子里面很笨拙地注射依赖,造成了一种“充血模型”—— hyperaemia domain model。
ajoo:
我想,现在大家伙对于组装/IOC都已经形成共识了吧。
既然,要设计一个“充血“模型,那么这里面的实体对象已经 是一个依赖组件的实体对象了,怎么把组件注入到实体对象中,虽然现在还没有一个公论/标准,但是我想这在实际开发中不会成为阻碍。 只不过,有些人实现得比较难看,有些比较有优雅而已。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics