DDD(序)充血模式比贫血模式好在哪里?


现在随着DDD越来越多地被人提出,代码开发的充血模式也在被越来越多的人使用,什么是充血模式?充血模式到底有哪些优点,又有什么缺点,到底要不要用充血模式?怎么用充血模式?

一、什么是充血模式

要了解充血模式,就需要了解与之相对的贫血模式,这个概念是国外的一位叫“Martin Fowler”的人提出的。

贫血模式

贫血模式是指业务对象里面只包含属性和 g e t , s e t \color{red}get,set getset方法,如下:

public class ImageTextAgeSO {
    private Date startTime;
    private Date endTime;
    private String institutionId;
        public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    public String getInstitutionId() {
        return institutionId;
    }

    public void setInstitutionId(String institutionId) {
        this.institutionId = institutionId;
    }
}

这种模式是我们常用的,在这种模式中,业务逻辑放在什么地方呢?service里面。

充血模式

充血模式是指除了包含getset方法外,还包含大部分的 业 务 逻 辑 \color{red}业务逻辑 (有些充血模型还包含持久化的逻辑),但是持久化的逻辑放在领域模型中,代码容易混乱,所以在此还是介绍只包含业务逻辑而不包含持久化的充血模式。

比如此时我们需要在SO中加入一个获取字段转换列表的逻辑,可以在SO中增加代码改为如下:

public class ImageTextAgeParam {
    private Date startTime;
    private Date endTime;
    private String institutionId;
    
    public List<ColumnDTO> columnDTOS() {
        List<ColumnDTO> columnDTOS = new ArrayList<>(10);
        ColumnDTO institutionIdcolumnDTO = new ColumnDTO();
        institutionIdcolumnDTO.setField("institution_id");
        institutionIdcolumnDTO.setFieldValue(Collections.singletonList(institutionId));
        institutionIdcolumnDTO.setOperator(Operator.EQUAL);
        columnDTOS.add(institutionIdcolumnDTO);
        return columnDTOS;
    }
}

这时候实体中就有了一些除get,set方法之外的逻辑。

像以上我们在SO中加入业务处理的逻辑,我们可以在Param或Query类中加入参数校验的逻辑,在VO中加入字段展示转换的逻辑等。

二、充血模式有哪些优点?

1、业务层次分明

属于某一个实体的业务处理逻辑就在该实体中,你或许会发表不同意见了,我把每个实体的处理写到每个实体对应的service中,不就也能实现相应实体的业务逻辑分离么?
我们就以上面的需求为例,你会把这个方法单独在service类里面拉一个接口方法,供别的类调用么?

    public List<ColumnDTO> columnDTOS() {
        List<ColumnDTO> columnDTOS = new ArrayList<>(10);
        ColumnDTO institutionIdcolumnDTO = new ColumnDTO();
        institutionIdcolumnDTO.setField("institution_id");
        institutionIdcolumnDTO.setFieldValue(Collections.singletonList(institutionId));
        institutionIdcolumnDTO.setOperator(Operator.EQUAL);
        columnDTOS.add(institutionIdcolumnDTO);
        return columnDTOS;
    }

不会有人这么干,我们大家的编码习惯,很自然的就将所有实体类的逻辑写到了同一个service中,而且这样提取的代码有一个特点,可复用性更高

2、代码可复用性好

当另一个接口又使用到这个类的逻辑的时候,相关逻辑都在业务类层,很容易找到,代码被复用的比例更高。

3、复合面向对象的编程逻辑

举例:比如一个订单类中,有商品单价和订单总价,当修改单价的时候,总价必须要一起修改,如果单单修改某一个值,会造成所有商品价格加起来与总价不一致。

这时候如果使用充血模式,把修改商品单价和总价的方法收拢掉,就不会出现上述不一致的问题。
代码如下:

public class ImageTextAgeBO {
    private BigDecimal price;
    private Integer count;
    private BigDecimal totalPrice;

	public void modifyPrice(BigDecimal newPrice){
		price = newPrice;
		totalPrice = price.multiply(count);
	}
}

这样的编码过程就更符合面向对象的逻辑,而不是把一个对象的某一个属性只看做一个单一的个体。

三、充血模型那么好,为何现在还都是贫血模型?

现在到处充斥着贫血模型,是因为受到早期Visual Basic语言的影响,VB是microsoft早期进行开发窗体的一款语言,通过getter和setter设置窗体属性即可。

但是由于microsoft强大的影响力,导致现在都是充斥着贫血模型。而现在越来越多的人意识到贫血模型在现在业务发展中的阻碍,由此充血模型开始越来越流行起来。

四、怎么用充血模式

使用充血模式的方式很简单,就是把你原来写在service层的业务逻辑写到你的 B O 类 中 或 D O \color{red}BO类中或DO BODO类中去,那么怎么区分哪些代码写到BO类中,哪些代码写到service类中呢?
其实区分方式很简单,记住以下原则即可:

  1. 当处理的业务都是本类相关的,可以将业务代码写到领域类中
    比如上面的modifyPrice,就非常适合放在领域类中比如上面的modifyPrice,就非常适合放在领域类中
  2. 如果处理的业务要依赖较多的外部数据,那么只能放在sercie类中
        public void insert(ChannelWebOpenConfigParam param) {
        ChannelWebOpenConfigEntity configEntity = channelWebOpenConfigDao.selectByChannelCode(param.getChannelCode());
        if (configEntity != null) {
            throw new WmBusinessException(
                    WmResult.error(ErrorCodeEnum.DATE_EXIST.getCode(), ErrorCodeEnum.DATE_EXIST.getMessage()));
        }
        ChannelWebOpenConfigEntity entity = param.convertParamToEntity();
        channelWebOpenConfigDao.insertSelective(entity);
    }
    

上面的方法中,先查询是否有数据,有数据抛出异常,没有数据则插入,这样跟数据库有交互,判断数据库中是否存在数据的逻辑就适合放在service中。

总结

什么是充血模式、贫血模式?

贫血模式是指只含有get\set方法的类,充血模式是指除了get/set方法外,还含有相关类的业务逻辑

充血模式有什么优缺点?
  • 优点:业务层次分明、代码可复用性好、更符合面向对象的编码逻辑+ 优点
  • 缺点:如果在领域类中,添加持久层逻辑,会使代码层次混乱,不知道哪些代码放在service层,哪些代码放在领域类中。如果领域类中无持久层逻辑,则容易划分代码层次
怎么使用充血模型?

与本类相关的业务逻辑放在领域类中,持久层相关的逻辑放在service中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值