`
ronghao
  • 浏览: 453952 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
E9473dd5-1985-3883-ac98-962354ca10b3
张小庆,在路上
浏览量:8712
社区版块
存档分类
最新评论

从贫血到充血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。

分享到:
评论
9 楼 ronghao 2008-11-27  
laiseeme 写道
你这折腾了半天又折腾回去了

其实上只是颠倒了下调用的位置,以前我需要调用service,现在只要调用domain 即可。但是实际应用中,我的代码中具有大量domain,它们如果去调用service会非常bt,现在直接调用相关domain,而且这些相关domain是hibernate帮我注入的,这样用起来就爽的多了。代码也更容易理解。
8 楼 laiseeme 2008-11-27  
你这折腾了半天又折腾回去了
7 楼 天下有鹏 2008-11-27  
楼主为什么不将WorkitemManager功能拆分,分为处理边界和业务逻辑?而Workitem保持纯粹的职责。

6 楼 mewleo 2008-11-27  
跟以前我考虑的对象装备模型有相似之处
在服务层中为贫血对象充血(装备逻辑方法)。

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

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

5 楼 crazy.j 2008-07-04  
ronghao 写道
确实是一个bad smell.当Domain Model代码中大量出现后,这种造型是很恐怖的。目前采取了一种处理方式:给所有Domain Model继承一个父类,在父类里集中管理所有Domain Model所依赖的services,在父类里进行造型。

也不好吧 实体类很可能会出现继承的情况,我觉得不如把实体类包装一下,合理的运用一下泛型,会省很多事
4 楼 ronghao 2008-07-04  
确实是一个bad smell.当Domain Model代码中大量出现后,这种造型是很恐怖的。目前采取了一种处理方式:给所有Domain Model继承一个父类,在父类里集中管理所有Domain Model所依赖的services,在父类里进行造型。
3 楼 bottom 2008-07-04  
very very very bad smell.

Agree!
2 楼 ronghao 2008-07-03  
Norther 写道
引用
WorkItemDAO workItemDAO=(WorkItemDAO)Container.getBean("workItemDAO"); 


very very very bad smell. : )

why? 耦合?依赖?
1 楼 Norther 2008-07-03  
引用
WorkItemDAO workItemDAO=(WorkItemDAO)Container.getBean("workItemDAO"); 


very very very bad smell. : )

相关推荐

    对贫血和充血模型的理解

    贫血模型和充血模型是两种在软件开发,尤其是面向对象编程中常见的设计策略,主要应用于领域驱动设计(Domain-Driven Design, DDD)中。这两种模型主要关注于业务逻辑和数据之间的关系,以及如何在软件架构中有效地...

    失血贫血充血胀血模型.docx

    这种设计将业务规则和操作完全分离到独立的服务或管理类中,通常称为Transaction Script。在失血模型中,domain object的角色非常有限,它们更像是数据容器,而非具有行为的对象。例如,`Item`实体类只负责存储拍卖...

    充血模型设想实现(2010/07/30更新)

    充血模型,也被称为“Rich Domain Model”,是领域驱动设计(DDD)中的一种核心概念。在软件开发中,领域模型是对业务领域的抽象和建模,它包含业务规则、逻辑和状态。充血模型强调对象应该拥有自己的行为和状态,而...

    缺铁性贫血病历模板.doc

    缺铁性贫血病历模板.doc

    论文研究 - 埃及患者铁缺乏性贫血和β地中海贫血特征的筛查和鉴别中的简单红细胞指标

    背景:小细胞性低铬性贫血的最常见原因是铁缺乏性贫血(IDA)和β-地中海贫血特征(β-TT)。 这项工作的目的是比较各种简单指标的有效性,以区分铁缺乏性贫血和β-地中海贫血性状。 对象和方法:通过全血筛查,血清...

    论文研究 - 资源贫乏地区的产后贫血和产后遗漏出血

    背景:产后出血是全世界孕产妇死亡的主要原因,贫血是其间接原因之一。 分娩期间贫血会增加发生PPH的风险,甚至在中度PPH后仍可能导致产妇死亡。 未确诊的PPH和产后贫血增加了社区晚期孕妇死亡的风险。 这项研究的...

    血液透析患者贫血的管理.ppt

    【血液透析患者贫血的管理】是针对慢性肾脏病(CKD)患者,特别是进行血液透析的患者,如何有效管理和治疗贫血问题的专业讲座或教材。贫血是CKD患者常见的并发症,随着肾功能的恶化,贫血的发生率逐渐增加。本资料...

    主治医师 (临床医学检验学)-贫血概述(A1-A2型题).doc

    根据这些特征,可能的贫血类型包括珠蛋白生成障碍性贫血、缺铁性贫血、巨幼细胞贫血、铁粒幼细胞贫血以及慢性感染或慢性炎症性贫血。 2. 案例2中的44岁女性患者,血红蛋白55g/L,红细胞2.10×10^12/L,根据世界卫生...

    贫血模型or领域模型

    贫血模型or领域模型的举例对比,让你初步了解贫血模型与领域模型的区别和概念

    肉苁蓉多糖对骨髓抑制性贫血小鼠造血调控的实验研究

    5. 实验结果:研究结果显示,低剂量和高剂量的肉苁蓉多糖均能显著促进骨髓细胞从G0/G1期向S期,以及从S期向G2/M期的转化,表明肉苁蓉多糖能够激活造血细胞周期,提高骨髓细胞的DNA合成及细胞增殖活性。此外,肉苁蓉...

    11丨实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?1

    总的来说,选择贫血模型还是充血模型,取决于项目的需求、团队的技术水平以及对代码质量的要求。在实践中,理解这两种模型的优缺点,并根据具体场景灵活选择,是提升软件开发效率和质量的关键。

    浅谈Asp.net中使用“充血模型”1

    在Asp.net开发中,"充血模型"是一种提倡领域对象拥有丰富行为和业务逻辑的设计模式,相对应于传统的"贫血模型"。"贫血模型"通常将数据模型、业务逻辑和数据访问分离,使得领域对象仅包含属性,而业务逻辑和数据操作...

    再生障碍性贫血教学查房.ppt

    再生障碍性贫血(AA)是一种骨髓造血功能衰竭的疾病,主要由药物、化学物质、放射线、病毒感染等因素引起,以全血细胞减少、进行性贫血、出血和感染为特征。在中国,该病常见于青壮年人群,男性患病率高于女性。病因...

    论文研究 - 镰状细胞性贫血患儿的胆石症:来自尼日利亚东北部的横断面分析

    背景:尼日利亚为全球镰状细胞性贫血负担贡献了30%。 胆石症通常发生在镰状细胞性贫血儿童中,可能仍未被诊断,模仿腹部血管闭塞性危机或因结石性胆囊炎而变得复杂。 早期识别患有胆石症的镰状细胞性贫血儿童可减少...

    小儿地中海贫血高凝患者血清丙二醛和sVCAM-1的相关性

    简介:在地中海贫血中反复输血和肠道铁吸收增加会导致铁超负荷,各种活性氧的催化产生会触发氧化应激。 内皮作为暴露于活性氧中的第一个器官,由于内皮功能障碍会触发高凝作用,其粘附分子会显着增加。 目的:探讨铁...

    基于GO的六边形架构框架,可支撑充血的领域模型范式代码实现.rar

    接下来,我们谈论“充血的领域模型”(Rich Domain Model)。在传统的贫血模型中,领域对象通常只包含数据,而业务逻辑则分散在服务层或其他地方。然而,在充血模型中,领域对象不仅包含了数据,还封装了大量的业务...

    完美医学课程之贫血.ppt

    形态分类包括大细胞性贫血(如巨幼细胞性贫血)、正细胞性贫血(如急性失血性贫血、溶血性贫血)和小细胞低色素性贫血(如缺铁性贫血、铁粒幼细胞贫血、地中海贫血)。病因和发病机制分类则涉及红细胞生成减少(如...

    医疗保健类化学药行业抗贫血药领域分析报告(研究报告).pdf

    公司分布:报告从多个维度对公司进行了分类,包括不同种类、不同档次、不同区域和不同应用领域,从而提供了抗贫血药物行业产品结构以及各类细分产品的市场需求的深入了解。报告详细调研了不同细分产品的市场容量、...

    抗贫血药行业(2021-2026)企业市场突围战略分析与建议.docx

    抗贫血药行业(2021-2026)的企业市场突围战略是企业在激烈的市场竞争中寻找增长点和竞争优势的关键所在。本报告旨在为抗贫血药行业的企业提供一系列深入的分析和实用的建议,以帮助他们在未来五年内实现市场的突破。 ...

Global site tag (gtag.js) - Google Analytics