锁定老帖子 主题:Domain Model 探索
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2004-12-24
实现接口 VS 继承父类
这里稍稍偏离一下主题,讨论一下接口同继承的问题。 什么时候使用接口?什么时候使用继承? 这似乎是个感觉和经验问题。或者我们会倾向于多使用接口,少使用继承。 可不可以再进一步呢? 以下是我的观点: “接口是调用方要求的结果,而继承则是实现方思考的产物。” 毕竟如果我们定义的接口没有被用到,那它就没有任何用处。 接口的目的在于制定虚的标准,从而使调用方不依赖于实现方。 而继承某个父类则多半是基于“偷懒“的考虑,已经存在的东西,我为什么不利用一下? 当然这样说是忽略了继承的真正用意--单点维护。 所以在定义XXXAct的接口时,需要多考虑一下,上层对象需要Act中的提供什么特性,会如何使用它。 接口属于调用方。 (待续) |
|
返回顶楼 | |
发表时间:2004-12-27
引用 形象一点来说,使用DIP模式,采取的是一种专家模式。
DIP的Act说的是:“CustomerCommitment你看看我现在的情况,还能运行吗?” 相反Facade模式,则是令人厌烦的唠叨模式。 Facade的Act说的是:“CustomerCommitment,现在执行的客户是XXX,业务是XXX,时间是XXX,...你能告诉我还能运行下去吗?” 显然DIP要潇洒得多。 不错的比喻! 仔细看了好久,学到的一点就是搞清楚谁依赖谁。 在协议层和act的协作例子中,假设了协议层相对act不稳定,那么就让协议层依赖于act。 我说一个有点无厘头的情况:act和协议层都在变化,它们两个都如此不稳定; 好了,既然你们两个都总是在变化,我就不把affrimCando这样的行为赋予你们,看你怎么办?接着我们将听到act的哀号“谁告诉我怎么办”? 接着我们的系统回应了:“兄弟,有我呢,请你把他身上的协议书拿给我看?还有兄弟你没在我这里注册,我不认识你,请告诉我你的军衔?”。然后act经过一翻努力后,把这些东西都交给了我们的系统,之后一切又回复正常。 那这是什么回事呢,原来我们又回到了facade模式,提供如下接口SystemBrain.ActCommitmentManager.affrimCando(Commitment,BusinessType )。 从affrimCando的调用者上来看,这些是系统的知识,系统可以告诉它, 注意这里提到了系统,好象不存在于 Decision Policy Commitment Operation Potential 这几个层次的任一个层次。它是否属于DDD的讨论范围呢? |
|
返回顶楼 | |
发表时间:2004-12-27
楼上的那位兄弟:你的意思好像是希望有个类来管理OPERATION AND COMMITMENT,让两者完全解耦,但实际情况是 COMMITMENT 肯定是对某种OPERATION 的COMMITMENT,另外新增加的这个管理类即要认识OPERATION 又要认识 COMMITMENT. 他的实现肯定会比较复杂,对于每一类的 OPERATION 和 COMMIMENT 都要有一个相应的实现,N*N的变化,得不偿失啊
|
|
返回顶楼 | |
发表时间:2004-12-27
partech 写道 透过Martin fowler网站,我拿到了
Domain Driven Design的发行前版本。 方便共享吗? |
|
返回顶楼 | |
发表时间:2004-12-27
业务对象的持久化
一个会引起争议的问题,是业务层是否会涉及业务对象持久化的概念。 答案是肯定的。 DDD中在描述The life cycle of a domain object时,给出了两种形式的持久化。 Store和Archive。我们使用的较多是Store。 但是这不代表业务层要依赖数据访问层。相反依赖关系应该倒过来。数据访问层依赖 业务层。通常我们使用Mapper实现,在hibernate中通过配置达到该目的。 要做到业务层不依赖于数据访问层,同样借助接口来完成。 在业务层定义数据访问的接口,为了方便,可以使用一个类来封装这些操作。 public interface CustomerFinder { Customer findByID(ID id); Customer findByCode(String code); DomainObjectCollection findByName(String name); ... } public class CustomerRepository { private static CustomerFinder finder = null; private static CustomerFinder getFinderInstance() { if (finder == null) { finder = (CustomerFinder)FinderRegistry.getFinder("CustomerFinder"); } return finder; } public static Customer findByID(ID id) { Customer obj = getFinderInstance().findByID(id); Check.require(obj != null, "未找到ID为:" + id.toString() + "对应的 Customer。"); return obj; } ... } 在数据访问层实现这些接口。因为是数据访问层依赖业务层,所以你可以采用多种技术来实现, 使用hibernate这样的开源项目,或者手工编写Mapper。 ID id 另外一个有争议的问题是Domain层是否要引入与业务无关的ID来标识不同的对象呢? 我的经验是在业务层引入ID的概念会使很多事情变得方便些。 如:Lazyload。 这是否不属于业务的范畴?是在概念上不属于业务。但在业务上 不是没有对应的概念。 例如:保存客户定购信息的订单,作为标识的就是订单号,这是给人使用的。 在使用电脑后,我们也给对象一个它能理解的统一标识,这就是ID。 另外不要使用业务上的概念作为主键和外键,因为它们本来就不是数据库的概念。 否则,会使得业务概念同数据库的概念混淆起来。 ID的使用通常会选择效率较高的long类型。 不过我们的实现走得更远,我们将其封装为ID对象。 (待续) |
|
返回顶楼 | |
发表时间:2004-12-29
Service层
现在我们向上看看将业务层包裹的服务层。 服务层是架设在应用层和业务层的桥梁,用来封装对业务层的访问,因此 可以把服务层看作中介,充当两个角色: 1.实现应用层接口要求的接口; 2.作为业务层的外观。 服务层的典型调用如下: public interface CustomerServices { void openCustomer(CustomerInfo cutomerInfo); void customerLostReport(String customerCode,Date expiringDate,String remark); CutomerBasicInfo getCutomerBasicInfo(String customerCode); ... } public class CustomerServicesImpl extends ServiceFacade implements CustomerServices { ... public void openCustomer(CustomerInfo cutomerInfo) { try { init(); OpenCustomerAct openCustomerAct = new OpenCustomerAct(customerInfo.name, customerInfo.code, customerInfo.address, customerInfo.plainpassword ... ); openCustomerAct.run(); commit(); } catch(Exception e) { throw ExceptionPostprocess(e); } } public void customerLostReport(String customerCode,Date expiringDate,String remark) { try { Check.require(customerCode != null && customerCode != "", "无效的客户代码:" + customerCode); init(); CustomerLostReportAct customerLostReportAct = new CustomerLostReportAct(customerCode, expiringDate, remark); customerLostReportAct.run(); commit(); } catch(Exception e) { throw ExceptionPostprocess(e); } } public CutomerBasicInfo getCutomerBasicInfo(String customerCode) { try { Check.require(customerCode != null && customerCode != "", "无效的客户代码:" + customerCode); init(); Customer customer = CustomerRepository.findByCode(customerCode); //这里选择的是在CustomerRepository外抛出CustomerNotFoundException异常, //另一种方法是在CustomerRepository中抛出CustomerNotFoundException异常。 //因为CustomerRepository在于通过客户代码查找对应的客户。至于是否应该抛出 //异常则交给业务层或服务层来处理。 //这里有很微妙的区别,抛出CustomerNotFoundException应该是谁的职责呢? //你的想法是什么?:) if(customer == null) throw new CustomerNotFoundException(customerCode); CutomerBasicInfo cutomerBasicInfo = CutomerBasicInfoAssembler.create(customer); return cutomerBasicInfo; } catch(Exception e) { throw ExceptionPostprocess(e); } } ... } 服务层的代码很简单,不是吗? 上面的代码可以通过AOP进一步的简化。使用AOP实现我希望代码象下面这样简单。 public class CustomerServicesImpl implements CustomerServices { ... public void openCustomer(CustomerInfo cutomerInfo) { OpenCustomerAct openCustomerAct = new OpenCustomerAct(customerInfo.name, customerInfo.code, customerInfo.address, customerInfo.plainpassword ... ); openCustomerAct.run(); } public void customerLostReport(String customerCode,Date expiringDate,String remark) { Check.require(customerCode != null && customerCode != "", "无效的客户代码:" + customerCode); CustomerLostReportAct customerLostReportAct = new CustomerLostReportAct(customerCode, expiringDate, remark); customerLostReportAct.run(); } public CutomerBasicInfo getCutomerBasicInfo(String customerCode) { Customer customer = CustomerRepository.findByCode(customerCode); if(customer == null) throw new CustomerNotFoundException(customerCode); CutomerBasicInfo cutomerBasicInfo = CutomerBasicInfoAssembler.create(customer); return cutomerBasicInfo; } DTO or Not 我认为是否使用DTO取决于项目的大小,开发团队的结构,以及对项目演变预期的评估结果。 不使用DTO而直接使用PO传递到应用层适用于一个人同时负责应用层和业务层的短期简单项目; 一旦采用该模式作为构架,我不知道业务层是否还能叫做面向对象。 原因如下: 1.使用PO承担DTO的职责传递到应用层,迫使PO不能包含业务逻辑,这样业务逻辑会暴露给应用层。 业务逻辑将由类似于XXXManager的类承担,这样看来似乎PO有了更多的复用机会,因为PO只包含getXXX同setXXX类似的属性。 然而这正类似面向过程模式的范例,使用方法操作结构,程序多少又回到了面向过程的方式。 2.将PO直接传递到应用层,迫使应用层依赖于业务层,如果一个人同时负责应用层和业务层那么问题不大; 如果是分别由不同的人开发,将使得应用层开发人员必须了解业务层对象结构的细节,增加了应用层开发人员的知识范围。 同时因为这种耦合,开发的并行受到影响,相互交流增多。 3.此外这也会使得业务层在构建PO时要特别小心,因为需要考虑传递到应用层效率问题,在构建业务层时需要 考虑应用层的需要解决的问题是不是有些奇怪? 有人会抱怨写XXXAssember太麻烦,我的经验是XXXAssembler都很简单。 我们使用手机,会发现大多数手机提供给的接口都是相同的,这包括0-9的数字键,绿色的接听键,红色的挂机键,还有一块显示屏。 无论我是拿到NOkIA,还是MOTO的手机,我都能使用,作为手机使用者我没有必要知道手机界面下的结构,不用关心 使用的是SmartPhone还是Symbian。 确实,应用层将服务层和业务层看作黑箱要比看作白箱好得多。 (待续) |
|
返回顶楼 | |
发表时间:2004-12-29
关注下,我觉得DDD是会热起来的。
不过我还没有仔细看过这本书,虽然一早就下载到乐, 放上来不知道会不会违规(应该是算盗版),怕怕,^_^ (刚看了下,压缩后6M多,放不上来,要是有人想要,我再分批放吧) 如果不能放上,那么有人提醒下我,免得做错事。 |
|
返回顶楼 | |
发表时间:2004-12-29
我想对你的设计提出两点建议:首先就是这个act,
引用 public class OpenCustomerAct extends CustomerAct { ... public void override doRun() { Customer customer = Customer.create(...); CapitalAccount capitalAccount = CapitalAccount.create(customer,...); capitalAccount.deposit(...); OpenCustomerLog.create(this); } ... } 采用一个一个template可能比较好.然后把这个方法放到capitalAccount 里面.这样让上层的调用者感觉比较清晰. 毕竟act的目的是提供扩展性,其实就是个拦截的功能类似AOP. :开户么,就是开以个账户 CapitalAccount:create(customer,...);{ new CustomerAct Tpleate{ .............. } } 还有就是service层 引用 public class CustomerServicesImpl extends ServiceFacade implements CustomerServices { ... public void openCustomer(CustomerInfo cutomerInfo) { try { init(); OpenCustomerAct openCustomerAct = new OpenCustomerAct(customerInfo.name, customerInfo.code, customerInfo.address, customerInfo.plainpassword ... ); openCustomerAct.run(); commit(); } catch(Exception e) { throw ExceptionPostprocess(e); } } 直接调用 CapitalAccount:create是不是更直接一些,更显得面向对象以些.把那谢复杂性隐藏到domain object:CapitalAccount里面. 为什么一定要用sevice呢?只有找到home的operation才需要service 不是么. | Policy | Commitment | Operation 其实都是domain object 的一部分 可以用strage来实现何必一定要分出呢部多层 不知道我的意见对不对,大这胆子抛块砖. |
|
返回顶楼 | |
发表时间:2004-12-29
开户不属于资金账户的职责,因为他同时还创建了一个客户对象。
另外使资金账户依赖开户Act也不是一个好想法,操作资金帐户的 Act会很多,并且还有可能增加,这样资金帐户就不封闭了。 Act的目的不是为了扩展,而是对业务活动的模拟,提供业务活动 的上下文。 前面已经说过Service层在于解耦应用层同业务层。只要接口不变, 业务层的重构不会影响到应用层。 分层的目的在于帮助我们更好的组织业务层,套用相关层次的含义。 可以得到概念依赖的业务层结构。假如你觉得不使用这些层次也能 弄清楚关系,那么这些层次对你的帮助就不大了。 |
|
返回顶楼 | |
发表时间:2004-12-29
引用 Act的目的不是为了扩展,而是对业务活动的模拟,提供业务活动 的上下文 我有点明白你的意思了: 你的open操作有两个对象(可能还有更多)三个操作:new customer;new accout ;dispose().所以说感觉把这个打的open对象放在哪个对象也不合适,所以需要以个上下文 于是你找到了一个act. act毕竟是个动词,你在这里定义了一个流程,ACT就是定义了如何进行开户的一个流程. service其实和act差不多,只是service 更想一个facade 面向客户,而act是面向业务的,必要的时候service可以为客户提供以个很大的接口,可以操作一揽子的act(operation) 如果我的理解是正确的话,这里有个问题从service 到act提供一种面向过程的思维方式,只是它的过程使用了一系列的接口规定了他的含义,然后有具体的实现. 世界是对象的世界? 我开始有点怀疑了. 从act到service有没有一种面向对象的方法实现,还要想想,也许这个过程本身就是过程化的,不需要对象式思维? 我有点糊涂了.还要仔细考虑一下. |
|
返回顶楼 | |