简单来说,就是domain object只有属性的getter/setter方法,没有任何业务逻辑。
@Data @ToString public class User { private Long id; private String username; private String password; private Integer status; private Date createdAt; private Date updatedAt; private Integer isDeleted; } ------------------------------------------------- public class UserService{ public boolean isActive(User user){ return user.getStatus().equals(StatusEnum.ACTIVE.getCode()); } }贫血模型
在失血模型基础之上聚合了业务领域行为,领域对象的状态变化停留在内存层面,不关心数据持久化。
@Data @ToString public class User { private Long id; private String username; private String password; private Integer status; private Date createdAt; private Date updatedAt; private Integer isDeleted; public boolean isActive(User user){ return user.getStatus().equals(StatusEnum.ACTIVE.getCode()); } public void setUsername(String username){ return username.trim(); } }
简单来说,就是domain ojbect包含了不依赖于持久化的领域逻辑,而那些依赖持久化的领域逻辑被分离到Service层。Service(业务逻辑,事务封装) --> DAO —> domain object
这种模型的优点:
1、各层单向依赖,结构清楚,易于实现和维护
2、设计简单易行,底层模型非常稳定
这种模型的缺点:
1、domain object的部分比较紧密依赖的持久化domain logic被分离到Service层,显得不够OO
2、Service层过于厚重
充血模型在贫血模型基础上,负责数据的持久化。
@Data @ToString public class User { private Long id; private String username; private String password; private Integer status; private Date createdAt; private Date updatedAt; private Integer isDeleted; private UserRepository userRepository; public boolean isActive(User user){ return user.getStatus().equals(StatusEnum.ACTIVE.getCode()); } public void setUsername(String username){ this.username = username.trim(); userRepository.update(user); } }
充血模型和第二种模型差不多,所不同的就是如何划分业务逻辑,即认为,绝大多业务逻辑都应该被放在domain object里面(包括持久化逻辑),而Service层应该是很薄的一层,仅仅封装事务和少量逻辑,不和DAO层打交道。
Service(事务封装) —> domain object <—> DAO
这种模型的优点:
1、更加符合OO的原则
2、Service层很薄,只充当“表面”的角色,不和DAO打交道。
这种模型的缺点:
1、DAO和domain object形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。
2、如何划分Service层逻辑和domain层逻辑是非常含混的,在实际项目中,由于设计和开发人员的水平差异,可能导致整个结构的混乱无序。
3、考虑到Service层的事务封装特性,Service层必须对所有的domain object的逻辑提供相应的事务封装方法,其结果就是Service完全重定义一遍所有的domain logic,非常烦琐,而且Service的事务化封装其意义就等于把OO的domain logic转换为过程的Service Transaction script。该充血模型辛辛苦苦在domain层实现的OO在Service层又变成了过程式,对于Web层程序员的角度来看,和贫血模型没有什么区别了 。
胀血模型基于充血模型的第三个缺点,有同学提出,干脆取消Service层,只剩下domain object和DAO两层,在domain object的domain logic上面封装事务。
domain object(事务封装,业务逻辑) <—> DAO
该模型优点:
1、简化了分层
2、也算符合OO
该模型缺点:
1、很多不是domain logic的service逻辑也被强行放入domain object ,引起了domain ojbect模型的不稳定
2、domain object暴露给web层过多的信息,可能引起意想不到的副作用。
在这四种模型当中,失血模型和胀血模型应该是不被提倡的。而贫血模型和充血模型从技术上来说,都已经是可行的了。但是我个人仍然主张使用贫血模型。
代码层面理解什么是贫血模型与充血模型?回答这个问题,我们从《重构》一书中的一个订单的开发场景,分别使用贫血模型与充血模型来实现,大家可以从中感受其差别理解它们的不同。
订单的场景 需求描述- 创建订单设置订单优惠
Order 类 , 只包含了属性的Getter,Setter方法
@Data public class Order { private long orderId; private int buyerId; private int sellerId; private BigDecimal amount; private BigDecimal shippingFee; private BigDecimal discountAmount; private BigDecimal payAmount; private String address; }
OrderService ,根据订单创建中的业务逻辑,组装order数据对象,最后进行持久化
public void createOrder(int buyerId,int sellerId,ListorderItems){ //新建一个Order数据对象 Order order = new Order(); order.setOrderId(1L); //算订单总金额 BigDecimal amount = orderItems.stream() .map(OrderItem::getPrice) .reduce(BigDecimal.ZERO,BigDecimal::add); order.setAmount(amount); //运费 order.setShippingFee(BigDecimal.TEN); //优惠金额 order.setDiscountAmount(BigDecimal.ZERO); //支付总额 = 订单总额 + 运费 - 优惠金额 BigDecimal payAmount = order.getAmount().add(order.getShippingFee()).subtract(order.getDiscountAmount()); order.setPayAmount(payAmount); //设置买卖家 order.setBuyerId(buyerId); order.setSellerId(sellerId); //设置收获地址 order.setAddress(JSON.toJSONString(new Address())); //写库 orderDao.insert(order); orderItems.forEach(orderItemDao::insert); }
在此种方式下,核心业务逻辑散落在OrderService中,比如获取订单总额与订单可支付金额是非常重要的业务逻辑,同时对象数据逻辑一同混编,在此种模式下,代码不能够直接反映业务,也违背了面向对象的SRP原则。
设置优惠
public void setDiscount(long orderId, BigDecimal discountAmount){ Order order = orderDao.find(orderId); order.setDiscountAmount(discountAmount); //从新计算支付金额 BigDecimal payAmount = order.getAmount().add(order.getShippingFee()).subtract(discountAmount); order.setPayAmount(payAmount); //orderDao => 通过主键更新订单信息 orderDao.updateByPrimaryKey(order); }
贫血模型在设置折扣时因为需要考虑到折扣引发的支付总额的变化,因此还需要在从新的有意识的计算支付总额,因为面向数据开发需要时刻考虑数据的联动关系,在这种模式下忘记了修改某项关联数据的情况可能是时有发生的。
订单场景充血模型实现Order 类,包含了业务关键属于以及行为,同时具有良好的封装性
@Getter public class Order { private long orderId; private int buyerId; private int sellerId; private BigDecimal shippingFee; private BigDecimal discountAmount; private Address address; private SetorderItems; //空构造,只是为了方便演示 public Order(){} public Order(long orderId,int buyerId ,int sellerId,Address address, Set orderItems){ this.orderId = orderId; this.buyerId = buyerId; this.sellerId = sellerId; this.address = address; this.orderItems = orderItems; } public void updateAddress(Address address){ this.address = address; } public BigDecimal getPayAmount(){ BigDecimal amount = getAmount(); BigDecimal payAmount = amount.add(shippingFee); if(Objects.nonNull(this.discountAmount)){ payAmount = payAmount.subtract(discountAmount); } return payAmount; } public BigDecimal getAmount(){ return orderItems.stream() .map(OrderItem::getPrice) .reduce(BigDecimal.ZERO,BigDecimal::add); } public void setShippingFee(BigDecimal shippingFee){ Preconditions.checkArgument(shippingFee.compareTo(BigDecimal.ZERO) >= 0, "运费不能为负"); this.shippingFee = shippingFee; } public void setDiscount(BigDecimal discountAmount){ Preconditions.checkArgument(discountAmount.compareTo(BigDecimal.ZERO) >= 0, "折扣金额不能为负"); this.discountAmount = discountAmount; } public Set getOrderItems() { return Collections.unmodifiableSet(orderItems); } }
OrderService , 仅仅负责流程的调度
public void createOrder(int buyerId, int sellerId, SetorderItems){ Order order = new Order(1L,buyerId,sellerId,new Address(),orderItems); //运费不随订单其它信息一同构造,因为运费可能在后期会进行修改,因此提供一个设置运费的方法 order.setShippingFee(BigDecimal.TEN); orderRepository.save(order); }
在此种模式下,Order类完成了业务逻辑的封装,OrderService仅负责业务逻辑与存储之间的流程编排,并不参与任何的业务逻辑,各模块间职责更明确。
设置优惠
public void setDiscount(long orderId, BigDecimal discountAmount){ Order order = orderRepository.find(orderId); order.setDiscount(discountAmount); orderRepository.save(order); }
在充血模型的模式下,只需设置具体的优惠金额,因为在Order类中已经封装了相关的计算逻辑,比如获取支付总额时,是实时通过优惠金额来计算的。
public BigDecimal getPayAmount(){ BigDecimal amount = getAmount(); BigDecimal payAmount = amount.add(shippingFee); if(Objects.nonNull(this.discountAmount)){ payAmount = payAmount.subtract(discountAmount); } return payAmount; }
写到这里,可能大家会有疑问,文章都在讲充血模型的业务,那数据怎么进行持久化?
数据持久化时我们通过封装的 OrderRepository 来进行持久化 *** 作,根据存储方式的不同提供不同的实现,以数据库举例,那么我们需要将Order转换为PO对象,也就是持久化对象,这时的持久化对象就是面向数据表的贫血模型对象。
比如下面的伪代码
public class OrderRepository { private final OrderDao orderDao; private final OrderItemDao orderItemDao; public OrderRepository(OrderDao orderDao, OrderItemDao orderItemDao) { this.orderDao = orderDao; this.orderItemDao = orderItemDao; } public void save(Order order){ // 在此处通过Order实体,创建数据对象 new OrderPO() ; new OrderItemPO(); // orderDao => 存储订单数据 // orderItemDao => 存储订单商品数据 } public Order find(long orderId){ //找到数据对象,OrderPO //找到数据对象,OrderItemPO //组合返回,order实体 return new Order(); } }
通过上面两种实现方式的对比,相信读者对两种模型已经有了明确的认识了,在贫血模型中,数据和业务逻辑是割裂的,而在充血模型中数据和业务是内聚的。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)