锁定老帖子 主题:再次小结领域模型的种种观点
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2005-12-16
ajoo 写道 我倒是不认为持久化与否是区分的关键。
相反,我认为应该把持久化完全摒除在考虑范围之外,因为那完全是实现细节。只要架设对象直接储存在内存中就够了。 有点武断,基于数据库的逻辑与内存完全不一样。 我不相信完全基于内存级别的设计能够在具体的开发中有什么指导意义。 |
|
返回顶楼 | |
发表时间:2005-12-16
ajoo 写道 不是难事。就看你做的好不好而已。要做成象spring 2.0那样要求domain object里面还要指定bean name这种反向依赖就不怎么样了。
这个问题,如果要允许new Teacher(),aspectj就是必须的。预编译的代价不管你怎么简化,怎么用ide,也只能减轻,不能消除。不过,逼不得已,做做看吧。 可以通过定制Classloader来使New出来的对象是一个Proxy对象,不需要静态织入吧? |
|
返回顶楼 | |
发表时间:2005-12-16
firebody 写道 引用 转账操作 transfer, 有疑义了,转账不依赖于某个特定的account,是一个抽象的通用操作,简单来说,它是一个无状态的操作,如果是你,你可能认为转账是一个银行账户的紧密业务逻辑,也确实如此,所以 account.transfer(account1 ,account2)。
no no .. 一般这样来 account.transfer(destAccount) . 一开始就强行假设,后面的所有论据都有点站不住脚。 AccountManager有存在的必要,正如你所说的。 事务确实是非常谨慎考虑的事情。然而存在AccountManager之后,会跟Account引起很多仍然可以争议的地方,这里是仁者见仁,智者见智。 另外对于你提到的一个操作逻辑,不知道该把它放到哪个对象身上,这点我想是一个经验和设计问题,几乎所有的操作肯定是基于某个对象促发的,因为一切都是对象。就所没有任何得***Service ,***Manager,全部是Domain model ,你仍然可以很OO的将Operation ,message定义到某个领域对象上,只要你对它提出需求,你就可以设计。 好吧,就不说transfer,比如说我想知道所有账户的存款平均余额: getAverageBalance(),这业务方法他和Account领域模型紧密关联吧,那么请问 account.getAverageBalance()合适吗? 你account对象没有理由知道我银行所有账户的存款余额呀? |
|
返回顶楼 | |
发表时间:2005-12-16
robbin 写道 firebody 写道 引用 转账操作 transfer, 有疑义了,转账不依赖于某个特定的account,是一个抽象的通用操作,简单来说,它是一个无状态的操作,如果是你,你可能认为转账是一个银行账户的紧密业务逻辑,也确实如此,所以 account.transfer(account1 ,account2)。
no no .. 一般这样来 account.transfer(destAccount) . 一开始就强行假设,后面的所有论据都有点站不住脚。 AccountManager有存在的必要,正如你所说的。 事务确实是非常谨慎考虑的事情。然而存在AccountManager之后,会跟Account引起很多仍然可以争议的地方,这里是仁者见仁,智者见智。 另外对于你提到的一个操作逻辑,不知道该把它放到哪个对象身上,这点我想是一个经验和设计问题,几乎所有的操作肯定是基于某个对象促发的,因为一切都是对象。就所没有任何得***Service ,***Manager,全部是Domain model ,你仍然可以很OO的将Operation ,message定义到某个领域对象上,只要你对它提出需求,你就可以设计。 好吧,就不说transfer,比如说我想知道所有账户的存款平均余额: getAverageBalance(),这业务方法他和Account领域模型紧密关联吧,那么请问 account.getAverageBalance()合适吗? 你account对象没有理由知道我银行所有账户的存款余额呀? 我认为这是一个查询。 我一般会设计一个Query接口。 不过不会这样做: Account----AcountQuery mama---->mamaQuery baba=----babaQuery ,会比较通用的一个Query接口,如果在JDK5.0上,这个会更好一些,因为可以定义返回的类型。 |
|
返回顶楼 | |
发表时间:2005-12-16
也就是说,XXXQuery.getAverageBalance()。也就是说,你的模型中还要增加Query这种模型,并且将要对DAO,domain,service和query统统进行事务控制,并且这许多API统统要暴露给web程序员了?
再进一步,好吧,比如我是银行领导,现在银行不是都对小额活期收年费了吗?那么请你告诉我,我们现在今年应该收多少年费上来? account.getTotalAnnualFee() 这已经包含很具体的业务含义了,你总不能说它不是业务逻辑了吧?从实现上来看,可不光是查询语句那么简单了,还需要根据不同的余额等级分级计算年费,DAO接口也好,Query接口也好,都无法覆盖他了,既然是业务逻辑,按照你的逻辑和数据放在一起的OO理论,那么当然应该放进account里面了,是吗? |
|
返回顶楼 | |
发表时间:2005-12-16
robbin 写道 也就是说,XXXQuery.getAverageBalance()。也就是说,你的模型中还要增加Query这种模型,并且将要对DAO,domain,service和query统统进行事务控制,并且这许多API统统要暴露给web程序员了?
再进一步,好吧,比如我是银行领导,现在银行不是都对小额活期收年费了吗?那么请你告诉我,我们现在今年应该收多少年费上来? account.getTotalAnnualFee() 这已经包含很具体的业务含义了,你总不能说它不是业务逻辑了吧?从实现上来看,可不光是查询语句那么简单了,还需要根据不同的余额等级分级计算年费,DAO接口也好,Query接口也好,都无法覆盖他了,既然是业务逻辑,按照你的逻辑和数据放在一起的OO理论,那么当然应该放进account里面了,是吗? 具体我还不知道你的这个需求,所以讨论好像也对不上好。 一般要统计的话,我没记错的应该是报表之类的。 报表一般是前面罗列所有的条目,后面又一个汇总数。 这时的getBalance()我更愿意看作是报表统计类的一个逻辑方法。 而不是一个查询。 如果仅仅是需要返回一个汇总条目,那么更可能的是一个存储过程的封装。 返回一个汇总条目实例,getbalance也仅仅是一个get field而已 。 如果是仅仅查询所有的余额。 那就放到对应的逻辑类上去,放到Query上不大合适。 |
|
返回顶楼 | |
发表时间:2005-12-16
如果要钻牛角尖到底的话,小额活期收年费这种逻辑不应该是帐户的业务而是银行的业务,所有该方法是属于"银行"对象的,xixi~
|
|
返回顶楼 | |
发表时间:2005-12-16
firebody 写道 ajoo 写道 我倒是不认为持久化与否是区分的关键。
相反,我认为应该把持久化完全摒除在考虑范围之外,因为那完全是实现细节。只要架设对象直接储存在内存中就够了。 有点武断,基于数据库的逻辑与内存完全不一样。 我不相信完全基于内存级别的设计能够在具体的开发中有什么指导意义。 数据如何存储是底层的实现逻辑。是否存在内存中有实际意义那是两码事。 如果你的domain设计依赖于“数据存在关系型数据库中”,那是你的设计有问题;如果设计不依赖数据库,那么,假设数据存在内存有助于简化问题。 |
|
返回顶楼 | |
发表时间:2005-12-16
someone 写道 ajoo 写道 不是难事。就看你做的好不好而已。要做成象spring 2.0那样要求domain object里面还要指定bean name这种反向依赖就不怎么样了。
这个问题,如果要允许new Teacher(),aspectj就是必须的。预编译的代价不管你怎么简化,怎么用ide,也只能减轻,不能消除。不过,逼不得已,做做看吧。 可以通过定制Classloader来使New出来的对象是一个Proxy对象,不需要静态织入吧? classloader怎么能拦截构造函数? 构造函数返回这个类型本身的对象是语言的强制契约,返回proxy违反了java的基本语义规则。 |
|
返回顶楼 | |
发表时间:2005-12-16
Hi,等不及firebody回复了。我先总结一下我的结论。
transfer这个例子举的不好,getAverageBalance也让firebody钻了空子,getTotalAnnualFee不知道firebody还有什么花样。但是我想表达的观点是: 充血模型号称要把操作和数据放在一起。认为把操作和数据分离开来的做法是不OO的。实际上这个结论非常空泛,并不是任何情况下都应该把操作和数据统统放在一起的。我前面举那么多例子就是证明这件事情。 这个问题需要分两个阶段来考虑: 一、系统分析阶段 在这个阶段,毫无疑问,只有一个Account抽象领域模型。 二、编码阶段 在这个阶段,一个抽象的Account领域模型并不是只映射到一个Java Class上面去的,即使抛开DAO接口分离持久化细节的考虑之外,也不应该只得到一个Account Class。 因为针对Account抽象领域模型来说,有两类操作:一类是有状态的操作;另一类是无状态操作,这两类操作从业务逻辑角度来说,都应该建模在Account抽象领域模型上,但是这两类操作到了编码阶段,就不应该统统塞进一个Class里面去。因为无状态的操作不是由具体的account对象触发的,也就是partech所谓的“被动”含义,应该是一个static的方法,但是IoC无法对static进行DI。这里受到了Java语法的限制。 如果没有IoC,那么我同意Account领域模型只映射一个Account Class。无状态操作定义为static的。但是IoC的情况下,我们必须进行职责分离,把无状态的操作分离到另外一个Class里面去,例如AccountManager,这个AccountManager可以是Singleton的。 在编码阶段Account Class和AccountManager Class不能孤立的看待,他们是互相协作的两个Class,共同代表了Account抽象领域模型。 因此结构是这样的: web action ---> domain object(account , accountManager) <---> DAO 事务控制放在account和accountmanager上面,所不同的是account是prototype的,有状态的,accountManager是singleton的,无状态的。 此外DAO是不应该被暴露给web层的,DAO是持久化实现代码,它不负责事务控制,它没有这个职责。同时请注意,所有的DAO接口方法统统都是无状态的,包括update,merge,persist操作。另外,很多DAO方法本身也是业务逻辑,就像getAverageBalance,他可以用一个DAO方法去实现,但是,getAverageBalance也有确切的业务含义,应该是一个无状态的,建模在Account抽象领域模型里面的domain logic。因此,accountManager也必须包含这个方法。简单来说,就是AccountManager应该封装DAO接口。 这样的充血模型才是合理的领域模型。在Spring2.0时代,我到是愿意尝试这一这种架构。而我觉得你提出的架构很混乱,不合理的地方也很多,经不起推敲。 |
|
返回顶楼 | |