充⾎模型编码实践
失⾎模型
简单来说,就是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){
Status().equals(Code());
}
}
贫⾎模型
在失⾎模型基础之上聚合了业务领域⾏为,领域对象的状态变化停留在内存层⾯,不关⼼数据持久化。
@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){
Status().equals(Code());
}
public void setUsername(String username){
im();
}
}
简单来说,就是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;bigdecimal转换为integer
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){
Status().equals(Code());
}
public void setUsername(String username){
this.username = im();
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层过多的信息,可能引起意想不到的副作⽤。
在这四种模型当中,失⾎模型和胀⾎模型应该是不被提倡的。⽽贫⾎模型和充⾎模型从技术上来说,都已经是可⾏的了。但是我个⼈仍然主张使⽤贫⾎模型。
代码层⾯理解什么是贫⾎模型与充⾎模型?
回答这个问题,我们从《重构》⼀书中的⼀个订单的开发场景,分别使⽤贫⾎模型与充⾎模型来实现,⼤家可以从中感受其差别理解它们的不同。
订单的场景
需求描述
1. 创建订单
2. 设置订单优惠
订单场景(失)贫⾎模型实现
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数据对象,最后进⾏持久化
/**
* 创建订单
* @param buyerId
* @param sellerId
* @param orderItems
*/
public void createOrder(int buyerId,int sellerId,List<OrderItem> orderItems){
//新建⼀个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 = Amount().ShippingFee()).DiscountAmount());
order.setPayAmount(payAmount);
//设置买卖家
order.setBuyerId(buyerId);
order.setSellerId(sellerId);
//设置收获地址
order.JSONString(new Address()));
//写库
orderDao.insert(order);
orderItems.forEach(orderItemDao::insert);
}
在此种⽅式下,核⼼业务逻辑散落在OrderService中,⽐如获取订单总额与订单可⽀付⾦额是⾮常重要的业务逻辑,同时对象数据逻辑⼀同混编,在此种模式下,代码不能够直接反映业务,也违背了⾯向对象的SRP原则。
设置优惠
/**
* 设置优惠
* @param orderId
* @param discountAmount
*/
public void setDiscount(long orderId, BigDecimal discountAmount){
Order order = orderDao.find(orderId);
order.setDiscountAmount(discountAmount);
//从新计算⽀付⾦额
BigDecimal payAmount = Amount().ShippingFee()).subtract(discountAmount);
order.setPayAmount(payAmount);
//orderDao => 通过主键更新订单信息
orderDao.updateByPrimaryKey(order);
}
贫⾎模型在设置折扣时因为需要考虑到折扣引发的⽀付总额的变化,因此还需要在从新的有意识的计算⽀付总额,因为⾯向数据开发需要时刻考虑数据的联动关系,在这种模式下忘记了修改某项关联数据的情况可能是时有发⽣的。
订单场景充⾎模型实现
Order 类,包含了业务关键属于以及⾏为,同时具有良好的封装性
/**
* @author zhengyin
* Created on 2021/10/18
*/
@Getter
@Getter
public class Order {
private long orderId;
private int buyerId;
private int sellerId;
private BigDecimal shippingFee;
private BigDecimal discountAmount;
private Address address;
private Set<OrderItem> orderItems;
//空构造,只是为了⽅便演⽰
public Order(){}
public Order(long orderId,int buyerId ,int sellerId,Address address, Set<OrderItem> orderItems){
this.buyerId = buyerId;
this.sellerId = sellerId;
this.address = address;
}
/**
* 更新收货地址
* @param address
*/
public void updateAddress(Address address){
this.address = address;
}
/**
* ⽀付总额等于订单总额 + 运费 - 优惠⾦额
* @return
*/
public BigDecimal getPayAmount(){
BigDecimal amount =getAmount();
BigDecimal payAmount = amount.add(shippingFee);
Null(this.discountAmount)){
payAmount = payAmount.subtract(discountAmount);
}
return payAmount;
}
/**
* 订单总价 = 订单商品的价格之和
*    amount 可否设置为⼀个实体属性?
*/
public BigDecimal getAmount(){
return orderItems.stream()
.map(OrderItem::getPrice)
.reduce(BigDecimal.ZERO,BigDecimal::add);
}
/**
* 运费不能为负
* @param shippingFee
*/
public void setShippingFee(BigDecimal shippingFee){
Preconditions.checkArgument(shippingFeepareTo(BigDecimal.ZERO)>=0,"运费不能为负");
this.shippingFee = shippingFee;
}
/**
* 设置优惠
* @param discountAmount
*/
public void setDiscount(BigDecimal discountAmount){
Preconditions.checkArgument(discountAmountpareTo(BigDecimal.ZERO)>=0,"折扣⾦额不能为负");
this.discountAmount = discountAmount;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。