首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网络技术 > 网络基础 >

从血亏到充血Domain Model

2012-11-06 
从贫血到充血Domain Model关于Domain Model的讨论已经非常多了,炒炒冷饭,这里是自己的一些做法。以Workitem

从贫血到充血Domain Model

关于Domain Model的讨论已经非常多了,炒炒冷饭,这里是自己的一些做法。
以Workitem(工作流里的工作项)作为例子。

最开始的做法:
一个实体类叫做Workitem,指的是一个工作项或者称为任务项
一个DAO类叫做WorkitemDao
一个业务逻辑类叫做WorkitemManager(或者叫做WorkitemService)

主要看看WorkitemManager,因为主要逻辑集中在这里

public class WorkitemManager {        private WorkItemDAO workItemDAO;    public void setWorkItemDAO(WorkItemDAO workItemDAO) {        this.workItemDAO = workItemDAO;    }       /**     * 提交工作项     * @param workitemId 工作项ID     */    public void commitWorkitem(String workitemId){            WorkItem workitem = workItemDAO.getWorkItem(workitemId);            //当前工作项结束        workitem.complete();        int sID = workitem.getSequenceId();        //找到所对应的节点        InstActivity instActivity=workitem.getInstActivity();        //查找是否存在下一工作项        WorkItem sequenceWorkitem = workItemDAO.findSequenceWorkItem(instActivity.getId(), sID + 1);        //如果不存在则触发节点流转        if (sequenceWorkitem == null) {            instActivity.signal();        }        //否则把下一工作项激活        else {            sequenceWorkitem.setExecutive();        }    }   }
?



Workitem类里有一些状态转换的逻辑,这样避免直接调用get/set属性方法

public class Workitem{        private int state = WorkitemInfo.PREPARE;        /**     * 委派工作项     */    public void commission() {        if (state != WorkitemInfo.EXECUTE && state != WorkitemInfo.SIGNINED                && state != WorkitemInfo.TOREAD&& state != WorkitemInfo.SUSPEND)            throw new WorkflowException(Messages.CANNOT_ALTER_WORKITEM_STATE);        setState(WorkitemInfo.COMMISSIONED);        setCommitted(new Timestamp(System.currentTimeMillis()));    }    /**     * 完成工作项     */    public void complete() {        if (state != WorkitemInfo.SIGNINED)            throw new WorkflowException(Messages.CANNOT_ALTER_WORKITEM_STATE);        setState(WorkitemInfo.COMPLETE);        setCompleted(new Timestamp(System.currentTimeMillis()));    }}
?



接下来的做法:

三个类不变,将WorkitemManager打平,将逻辑移动到Workitem

public class WorkitemManager {        private WorkItemDAO workItemDAO;    public void setWorkItemDAO(WorkItemDAO workItemDAO) {        this.workItemDAO = workItemDAO;    }       /**     * 提交工作项     * @param workitemId 工作项ID     */    public void commitWorkitem(String workitemId){            WorkItem workitem = workItemDAO.getWorkItem(workitemId);            //当前工作项提交        workitem.commit();    }   }
?


实际上此时WorkitemManager的功能非常有限,仅仅是事务边界和获取workitem对象,甚至在一些情况下可以省略。

通过一个Container类将spring的applicationContext进行封装,然后通过getBean()的静态方法即可访问被spring所管理的bean。实际是将workItemDAO隐式注入了Workitem。

public class Workitem{        /**     * 提交工作项     */    public void commit() {        if (state != WorkitemInfo.EXECUTE && state != WorkitemInfo.SIGNINED                && state != WorkitemInfo.TOREAD&& state != WorkitemInfo.SUSPEND)            throw new WorkflowException(Messages.CANNOT_ALTER_WORKITEM_STATE);        setState(WorkitemInfo.COMMISSIONED);        setCommitted(new Timestamp(System.currentTimeMillis()));        int sID = workitem.getSequenceId();        WorkItemDAO workItemDAO=(WorkItemDAO)Container.getBean("workItemDAO");        //查找是否存在下一工作项        WorkItem sequenceWorkitem = workItemDAO.findSequenceWorkItem(instActivity.getId(), sID + 1);        //如果不存在则触发节点流转        if (sequenceWorkitem == null) {            instActivity.signal();        }        //否则把下一工作项激活        else {            sequenceWorkitem.setExecutive();        }    }}
?



这样带来的好处是业务逻辑全部被封装到Domain Model,Domain Model之间的交互变得非常的简单,没有频繁的set/get,直接调用有业务语义的Domain Model的方法即可。问题在于单元测试时脱离不了spring的容器,workItemDAO需要stub。我觉得这个问题不大,反而是Domain Model开始变得臃肿,在业务逻辑复杂时代码行急剧膨胀。

现在的做法

以上三个类保持不变,增加一个类WorkitemExecutor,将业务逻辑移步。

public class Workitem{        /**     * 提交工作项     */    public void commit() {        if (state != WorkitemInfo.EXECUTE && state != WorkitemInfo.SIGNINED                && state != WorkitemInfo.TOREAD&& state != WorkitemInfo.SUSPEND)            throw new WorkflowException(Messages.CANNOT_ALTER_WORKITEM_STATE);        setState(WorkitemInfo.COMMISSIONED);        setCommitted(new Timestamp(System.currentTimeMillis()));        WorkitemExecutor workitemExecutor=(WorkitemExecutor)Container.getBean("workitemExecutor");        workitemExecutor.commitWorkitem(this);    }}
?


public class WorkitemExecutor {        private WorkItemDAO workItemDAO;    public void setWorkItemDAO(WorkItemDAO workItemDAO) {        this.workItemDAO = workItemDAO;    }       /**     * 提交工作项     * @param workitemId 工作项ID     */    public void commitWorkitem(Workitem workitem){        int sID = workitem.getSequenceId();        //找到所对应的节点        InstActivity instActivity=workitem.getInstActivity();        //查找是否存在下一工作项        WorkItem sequenceWorkitem = workItemDAO.findSequenceWorkItem(instActivity.getId(), sID + 1);        //如果不存在则触发节点流转        if (sequenceWorkitem == null) {            instActivity.signal();        }        //否则把下一工作项激活        else {            sequenceWorkitem.setExecutive();        }    }   }
?



将业务逻辑拆分成两部分,一部分在Workitem,另一部分委托给WorkitemExecutor。实际上是Domain Model将复杂逻辑的情况重新外包出去。调用的时候,面向的接口还是Domain Model的方法。注意到WorkitemExecutor和WorkitemManager的API是非常相似的。实际可以这样认为,传统的方式
Client->(Business Facade)->service(Business Logic 部分依赖Domain Model)->Data Access(DAO)。
现在的方式
Client->(Business Facade)->Domain Model->service->Data Access(DAO)。

另外,在返回client端的查询的时候还是倾向于直接调用DAO,而不是通过Domain Model。

耦合?依赖?
3 楼 bottom 2008-07-04   very very very bad smell.

Agree! 4 楼 ronghao 2008-07-04   确实是一个bad smell.当Domain Model代码中大量出现后,这种造型是很恐怖的。目前采取了一种处理方式:给所有Domain Model继承一个父类,在父类里集中管理所有Domain Model所依赖的services,在父类里进行造型。 5 楼 crazy.j 2008-07-04   ronghao 写道确实是一个bad smell.当Domain Model代码中大量出现后,这种造型是很恐怖的。目前采取了一种处理方式:给所有Domain Model继承一个父类,在父类里集中管理所有Domain Model所依赖的services,在父类里进行造型。
也不好吧 实体类很可能会出现继承的情况,我觉得不如把实体类包装一下,合理的运用一下泛型,会省很多事 6 楼 mewleo 2008-11-27   跟以前我考虑的对象装备模型有相似之处
在服务层中为贫血对象充血(装备逻辑方法)。

但自从深度使用动态语言后,才发现自己被Java害了

曾经一个不是牛人的牛人对我说过,面向对象只是一个思想
是解决逻辑问题的一个思路,不可能在代码中完美的体现OO,
现在想来,到底Java和JavaScript谁更OO呢?

7 楼 天下有鹏 2008-11-27   楼主为什么不将WorkitemManager功能拆分,分为处理边界和业务逻辑?而Workitem保持纯粹的职责。

8 楼 laiseeme 2008-11-27   你这折腾了半天又折腾回去了 9 楼 ronghao 2008-11-27   laiseeme 写道你这折腾了半天又折腾回去了
其实上只是颠倒了下调用的位置,以前我需要调用service,现在只要调用domain 即可。但是实际应用中,我的代码中具有大量domain,它们如果去调用service会非常bt,现在直接调用相关domain,而且这些相关domain是hibernate帮我注入的,这样用起来就爽的多了。代码也更容易理解。

热点排行
Bad Request.