目录
现在随着DDD越来越多地被人提出,代码开发的充血模式也在被越来越多的人使用,什么是充血模式?充血模式到底有哪些优点,又有什么缺点,到底要不要用充血模式?怎么用充血模式?
一、什么是充血模式
要了解充血模式,就需要了解与之相对的贫血模式,这个概念是国外的一位叫“Martin Fowler”的人提出的。
贫血模式
贫血模式是指业务对象里面只包含属性和 g e t , s e t \color{red}get,set get,set方法,如下:
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
BO类中或DO类中去,那么怎么区分哪些代码写到BO类中,哪些代码写到service类中呢?
其实区分方式很简单,记住以下原则即可:
- 当处理的业务都是本类相关的,可以将业务代码写到领域类中
比如上面的modifyPrice,就非常适合放在领域类中比如上面的modifyPrice,就非常适合放在领域类中 - 如果处理的业务要依赖较多的外部数据,那么只能放在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中。