`

贫血模型和充血模型

阅读更多

这两个概念是早些时候Martin Fowler总结出来的两种常见模型设计类型,没有说谁好谁不好,为不同的模型类别选择合适的场景是设计者的工作。没有工具本身的问题,只有工具使用者的问题。

 

 

贫血模型是指领域对象里只有get和set方法(POJO),所有的业务逻辑都不包含在内而是放在Business Logic层。

 


 

优点是系统的层次结构清楚,各层之间单向依赖,Client->(Business Facade)->Business Logic->Data Access Object。可见,领域对象几乎只作传输介质之用,不会影响到层次的划分。

 

该模型的缺点是不够面向对象,领域对象只是作为保存状态或者传递状态使用,它是没有生命的,只有数据没有行为的对象不是真正的对象,在Business Logic里面处理所有的业务逻辑,对于细粒度的逻辑处理,通过增加一层Facade达到门面包装的效果。

 

在使用Spring的时候,通常暗示着你使用了贫血模型,我们把Domain类用来单纯地存储数据,Spring管不着这些类的注入和管理,Spring关心的逻辑层(比如单例的被池化了的Business Logic层)可以被设计成singleton的bean。

 

假使我们这里逆天而行,硬要在Domain类中提供业务逻辑方法,那么我们在使用Spring构造这样的数据bean的时候就遇到许多麻烦,比如:bean之间的引用,可能引起大范围的bean之间的嵌套构造器的调用。

 

贫血模型实施的最大难度在于如何梳理好Business Logic层内部的划分关系,由于该层会比较庞大,边界不易控制,内部的各个模块之间的依赖关系不易管理,可以考虑这样这样的实现思路:

(1)铺设扁平的原子业务逻辑层,即简单的CRUD操作(含批量数据操作);

(2)特定业务清晰的逻辑通过Facade层来组装原子操作实现。

(3)给业务逻辑层实施模块划分,保持模块之间的松耦合的关系。

 

举例说明:

原子业务逻辑层(Service)提供了用户模型的条件查询方法:

List<User> queryUser(Condition con)

Facade层则提供了一种特定的业务场景的分子接口,满足18岁的中国公民,内部实现调用的正是上述的原子接口:

List<User> queryAdultChinese()

Facade、Service层纵向划分为几个大的领域包:用户、内容和产品。

 

 

充血模型层次结构和上面的差不多,不过大多业务逻辑和持久化放在Domain Object里面,Business Logic只是简单封装部分业务逻辑以及控制事务、权限等,这样层次结构就变成Client->(Business Facade)->Business Logic->Domain Object->Data Access Object。

 


 

它的优点是面向对象,Business Logic符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重。

 

缺点是如何划分业务逻辑,什么样的逻辑应该放在Domain Object中,什么样的业务逻辑应该放在Business Logic中,这是很含糊的。即使划分好了业务逻辑,由于分散在Business Logic和Domain Object层中,不能更好的分模块开发。熟悉业务逻辑的开发人员需要渗透到Domain Logic中去,而在Domian Logic又包含了持久化,对于开发者来说这十分混乱。  其次,如果Business Logic要控制事务并且为上层提供一个统一的服务调用入口点,它就必须把在Domain Logic里实现的业务逻辑全部重新包装一遍,完全属于重复劳动。

 

使用RoR开发时, 每一个领域模型对象都可以具备自己的基础业务方法,通常满足充血模型的特征。充血模型更加适合较复杂业务逻辑的设计开发。

 

充血模型的层次和模块的划分是一门学问,对开发人员要求亦较高,可以考虑定义这样的一些规则:

(1)事务控制不要放在领域模型的对象中实现,可以放在facade中完成。

(2)领域模型对象中只保留该模型驱动的一般方法,对于业务特征明显的特异场景方法调用放在facade中完成。

 

 

万事都不是绝对的,也有一些看起来不易解决的问题。例如,考虑到性能的需要,我需要一次查询出满足某种条件的用户和某种条件的产品,他们二者之间通过订购关系关联起来,可能发现这种情形下,上述的模型层次划分变得无解了……

 

怎么办呢?包括以上种种,欢迎大家讨论。

 

  • 大小: 21 KB
  • 大小: 15.7 KB
3
0
分享到:
评论
10 楼 huashuizhuhui 2012-02-05  
哈哈 这个问题我是这样解决的
CQRS查询命令分离
http://user.qzone.qq.com/46580583?ADUIN=46580583&ADSESSION=1328406420&ADTAG=CLIENT.QQ.3493_MyTip.0&ptlang=2052#!app=4
9 楼 RayChase 2012-02-04  
RayChase 写道
feikiss 写道

外部需要关注异常时,可以捕获;不想关注时,可以无视,到最外部的异常拦截器统一处理即可。
参数校验行为的发生可以使用AOP来完成。

是说把需要校验的内容单独拿出来,由单独的类来专一来处理?

首先,入参的校验,被称作Guard Statement,本身就可以属于方法的一部分,你这样的实现本来就未尝不可。

如果你希望把入参校验单独抽取出来,那么,这样的事情做得少一点为妙,比如仅仅是组件对外部暴露的接口才做。

如果其中的方法参数没有封装到对象里面去,可以参见Struts2的Action中对参数校验的实现(validate-xxx.xml),它把参数检查的规则放置到配置文件里面去了;

如果你能把方法入参做一个包装,单纯使用AOP来对参数进行校验的时候,起码看起来会优美得多:
setFacilityHex(SetFacilityHexEvent)
而其中的SetFacilityHexEvent定义如下,validate的调用借由AOP来完成:
class SetFacilityHexEvent{
    String facilityHex;
    public void validate(){
        …… //此处进行参数校验
    }
}
8 楼 feikiss 2012-02-04  
feikiss 写道

外部需要关注异常时,可以捕获;不想关注时,可以无视,到最外部的异常拦截器统一处理即可。
参数校验行为的发生可以使用AOP来完成。

是说把需要校验的内容单独拿出来,由单独的类来专一来处理?
7 楼 stevenjohn 2011-12-30  
你说的这些貌似都用过的,呵呵,不过看下能够加深映像
6 楼 RayChase 2011-12-28  
feikiss 写道
我在一项目中想为setter方法添加一些判断,但是在判断后发现是非法传值时又该怎么返回给客户端呢?我想到的解决方案就是throw exception...
public void setFacilityHex(String facilityHex)
			throws WrongFormatOfHexStrException {
		if (!volidate(facilityHex)) {//校验后发现为非法输入。。。。。。
			throw new WrongFormatOfHexStrException(
					"The parameter of facilityHex:'"
							+ facilityHex
							+ "' is uncorrect,"
							+ "please make sure that it is like this:0xA2 0XA2 0xa2 0Xa2 a2 or A2");
		}
		if (facilityHex.startsWith("0x") || facilityHex.startsWith("0X")) {
			this.facilityHex = facilityHex;
		} else {
			this.facilityHex = "0x" + facilityHex;
		}
	}

不知道有什么更好的解决方案没?

你说的参数输入错误属于异常流程,在开发人员设计和编码过程中,我们尽量避免这些旁枝错节对开发人员的干扰,在业务实现中,他们最好只需要关注主流程即可。
因此,定义一个或多个运行时异常就是一种很好的方式,给每一个你这里关注的对象实现一个validate接口,接口用来判断参数输入的正确性,错误的输入将抛出运行时异常,错误信息通过异常机制往外传递。
外部需要关注异常时,可以捕获;不想关注时,可以无视,到最外部的异常拦截器统一处理即可。
参数校验行为的发生可以使用AOP来完成。
5 楼 RayChase 2011-12-28  
energykey 写道
贫血模型里的service和dao都可以看做是对象啊,面向对象并没有说一个类要包揽所有的属性和行为吧?service可以看做是对行为的抽象,dao可以看做是数据持久化这个行为的抽象。

不过实践中有时候为了方便我也会加一些业务逻辑到get方法里,
比如简单的sex为int型,
加一个
getSexVo(){
if(sex==1)
return "男";
}
当然这是小项目里为了快速实现,大型项目里会有专门的类来处理这类问题。

这个问题我觉得就像数据库的范式设计一样,不可能过高的追求OOP,比如service中一个方法的参数,是该直接传对象还是分别传该对象需要操作的属性,比如
modifyAge(int age, int userId);
modifyAge(user);

你觉得哪种更能清晰的体现这个行为?
我一般都是业务层分开传递参数,DAO的方法一律传递对象,因为DAO只接受对象然后持久化到数据库,没有业务逻辑,但是涉及到分页,批量操作的时候又是例外了。。。

总之没有一剂良药能包治百病啊,,,不是还有AOP么。。。我觉得实践中需要不断的总结,提炼出自己的一套良药。

水平有限,欢迎指正。

首先,你说得非常正确,不可能过高地追求OOP,也没有必要。

其次,对象,包括属性(或者说数据)和行为,你说的service和dao,常常只包含了行为,没有了属性,这和评论一楼的同学说的正相反(他说的POJO则是只包含了属性,没有行为)

就你的问题而言,通常我会倾向于传user这样的对象,本身是基于对象模型的操作,再者也便于参数扩展。
4 楼 RayChase 2011-12-28  
powerspring 写道
“贫血模型的缺点是不够面向对象,”--之前,俺很困惑的就是这点。POJO不是OOP呀。

对象,除了属性,还有行为。贫血模型只剩下属性和相应属性的get/set方法,失去了行为的能力,可以看做是对象的退化,退化成了没有生命的数据容器。
3 楼 feikiss 2011-12-28  
我在一项目中想为setter方法添加一些判断,但是在判断后发现是非法传值时又该怎么返回给客户端呢?我想到的解决方案就是throw exception...
public void setFacilityHex(String facilityHex)
			throws WrongFormatOfHexStrException {
		if (!volidate(facilityHex)) {//校验后发现为非法输入。。。。。。
			throw new WrongFormatOfHexStrException(
					"The parameter of facilityHex:'"
							+ facilityHex
							+ "' is uncorrect,"
							+ "please make sure that it is like this:0xA2 0XA2 0xa2 0Xa2 a2 or A2");
		}
		if (facilityHex.startsWith("0x") || facilityHex.startsWith("0X")) {
			this.facilityHex = facilityHex;
		} else {
			this.facilityHex = "0x" + facilityHex;
		}
	}

不知道有什么更好的解决方案没?
2 楼 energykey 2011-12-28  
贫血模型里的service和dao都可以看做是对象啊,面向对象并没有说一个类要包揽所有的属性和行为吧?service可以看做是对行为的抽象,dao可以看做是数据持久化这个行为的抽象。

不过实践中有时候为了方便我也会加一些业务逻辑到get方法里,
比如简单的sex为int型,
加一个
getSexVo(){
if(sex==1)
return "男";
}
当然这是小项目里为了快速实现,大型项目里会有专门的类来处理这类问题。

这个问题我觉得就像数据库的范式设计一样,不可能过高的追求OOP,比如service中一个方法的参数,是该直接传对象还是分别传该对象需要操作的属性,比如
modifyAge(int age, int userId);
modifyAge(user);

你觉得哪种更能清晰的体现这个行为?
我一般都是业务层分开传递参数,DAO的方法一律传递对象,因为DAO只接受对象然后持久化到数据库,没有业务逻辑,但是涉及到分页,批量操作的时候又是例外了。。。

总之没有一剂良药能包治百病啊,,,不是还有AOP么。。。我觉得实践中需要不断的总结,提炼出自己的一套良药。

水平有限,欢迎指正。
1 楼 powerspring 2011-12-28  
“贫血模型的缺点是不够面向对象,”--之前,俺很困惑的就是这点。POJO不是OOP呀。

相关推荐

    对贫血和充血模型的理解

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

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

    贫血模型可以看作是失血模型的一种变体,也是将数据模型和业务逻辑分离,但领域对象可能会包含一些基本的数据验证逻辑。与失血模型相比,贫血模型的领域对象稍有"血色",但仍然缺乏复杂的行为。 优点: 1. 简单明了...

    领域模型驱动设计1553265830.pdf

    - 领域模型设计:采用充血模型而非贫血模型,并且在设计中融合设计模式、流程编排、事件驱动等元素。 - 强化单测:确保代码的质量,通过单元测试来保证各个领域模型的正确性和稳定性。 - 持续重构:在业务生命周期内...

    领域驱动设计案例-盒马实践

    领域模型可以分为失血模型、贫血模型和充血模型三种类型。 失血模型 失血模型是基于数据库的领域设计方式,它指的是使用 POJO 数据对象来存储业务数据。在失血模型中,业务逻辑是分散的,分布在多个地方。 贫血...

    基于领域分析设计的架构规范.docx

    总结来说,基于领域分析设计的架构规范着重于如何通过读写隔离优化查询操作,利用状态图揭示核心业务逻辑,并通过对比贫血模型和充血模型,强调了业务逻辑应与数据结构紧密结合,以提升软件设计的质量和效率。...

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

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

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

    充血模型强调对象应该拥有自己的行为和状态,而不是简单地作为数据容器。这个模型与贫血模型相对,后者通常由无行为的POJO(Plain Old Java Object)或DTO(Data Transfer Object)组成,业务逻辑被分离到服务层。 ...

    阿里巴巴java开发规范手册1.5.0.zip

    6. **代码组织**:提倡模块化和分层设计,如贫血模型和充血模型的选择,以及MVC、三层架构等设计。强调接口的定义应清晰,实现应简洁,避免过度封装。 7. **日志记录**:推荐使用合适的日志级别,如DEBUG、INFO、...

    ibatis 例子

    在实际应用中,应遵循良好的设计原则,如贫血模型和充血模型的选择,避免过多的嵌套SQL,合理使用缓存,以及充分利用Oracle的索引和分区等特性来优化性能。 总的来说,`ibatis`提供了一种灵活的、易于维护的数据库...

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

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

    领域模型说明及范例代码.zip

    领域模型(Domain Model)和贫血模型(Anemic Domain Model)是两种常见的模型设计模式,它们各有特点,适用于不同的场景。本资料包旨在通过实例对比,帮助初学者理解这两种模型的区别和概念,并提供实际的Java代码...

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

    在传统的贫血模型中,领域对象通常只包含数据,而业务逻辑则分散在服务层或其他地方。然而,在充血模型中,领域对象不仅包含了数据,还封装了大量的业务逻辑。这种方法使得领域模型更加生动且有力量,因为它们可以...

    smbms:SSM教程超市

    此外,还能接触到常见的设计模式,如贫血模型和充血模型,以及如何进行事务管理和错误处理。 7. **数据库设计**:SMBMS系统可能包括商品表、用户表、订单表等多个数据库表,学习者可以借此机会了解数据库设计的基本...

    软件架构技术公司内部交流

    架构设计过程中,还会讨论不同的模型类型,如贫血模型、充血模型(领域驱动模型)和胀血模型。贫血模型中,业务逻辑主要集中在Service层,而DO(Data Object)仅包含数据;充血模型则将业务逻辑放在DO中,Service层...

    大白话领域驱动设计DDD视频教程

    贫血模型的优缺点? DDD提倡的充血模型是什么? 体会下充血模型开发微信钱包系统 聚合和聚合根是什么? 领域事件是什么? 看看领域事件的本质(解耦,异步,削峰) 工厂和资源库的作用? 领域服务是什么? 通过用例...

    tbl-demo-service-master.zip

    如果领域模型只是用来处理简单的逻辑(比如贫血模型),那么领域模型的作用微乎其微,甚至可以忽略,数据转换的成本比领域模型带来的好处还多,这种情况其实就是在原有的分层架构中多加了一层,增加了项目的复杂性和...

    DDD领域驱动设计day02.pdf

    领域驱动设计中的领域模型包括充血模型和贫血模型两种不同的建模方式。贫血模型主要存在于传统分层架构中,其特点是实体类中几乎没有业务逻辑,主要通过getter和setter方法来访问属性。这种模式下,业务逻辑分散在...

    DDD领域驱动设计day01.pdf

    7. **贫血模型**与**充血模型**:贫血模型是指对象主要负责数据传输,业务逻辑存在于服务层;充血模型则强调领域对象内含业务逻辑,对象自身具有行为。 8. **微服务**:DDD常与微服务架构结合,每个微服务专注于一...

Global site tag (gtag.js) - Google Analytics