ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

面试官:你的实体类全是 Get/Set,这和直接操作数据库有什么区别?

面试官:你的实体类全是 Get/Set,这和直接操作数据库有什么区别? 面试官你的实体类全是 Get/Set这和直接操作数据库有什么区别翻开大多数同学的校招项目源码总能看到类似的Order类DatapublicclassOrder{privateLongid;privateLonguserId;privateIntegerstatus;privateBigDecimalamount;// 省略一堆自动生成的属性}紧接着在OrderServiceImpl里会有一大坨这样的代码publicvoidcancelOrder(LongorderId,LonguserId){OrderorderorderMapper.selectById(orderId);if(ordernull){thrownewBizException(订单不存在);}if(!order.getUserId().equals(userId)){thrownewBizException(无权操作);}// 只有待支付状态才能取消if(order.getStatus()!0){thrownewBizException(订单状态不支持取消);}order.setStatus(2);// 2表示已取消orderMapper.updateById(order);// 可能还有退库存、退优惠券等逻辑...}这就是典型的“贫血模型”Anemic Domain Model。你的Order类只是一个承载数据的容器和数据库里的表结构一一对应。所有的业务规则、状态流转全塞在 Service 层里。你可能觉得这没什么问题大家都这么写。但如果在“退款”接口、“后台强制关单”接口里你都需要判断订单状态这些散落的if (order.getStatus() ! 0)就会在代码库里到处复制粘贴。哪天产品经理说“部分付款的订单也能取消”你就得把所有涉及订单取消的 Service 代码全找出来改一遍。漏改一处就是个 P0 级线上事故。这和直接在数据库里敲UPDATE orders SET status 2 WHERE id ? AND status 0没什么本质区别。你用面向对象的语言写着面向过程的面条代码。把行为还给实体充血模型DDD领域驱动设计给出的解法很直接对象不应该只有状态还必须有行为。谁拥有数据谁就该负责处理数据。我们把取消订单的逻辑收拢到Order实体内部publicclassOrder{privateLongid;privateLonguserId;privateOrderStatusstatus;// 用枚举替代魔术数字// 构造函数、属性等.../** * 领域行为取消订单 */publicvoidcancel(LongoperatorId){if(!this.userId.equals(operatorId)){thrownewBizException(无权操作该订单);}if(this.status!OrderStatus.PENDING_PAY){thrownewBizException(当前状态无法取消订单);}this.statusOrderStatus.CANCELLED;// this.updatedAt LocalDateTime.now();}}经过这样的改造Service 层的代码会变成这样publicvoidcancelOrder(LongorderId,LonguserId){OrderorderorderMapper.selectById(orderId);if(ordernull){thrownewBizException(订单不存在);}// 业务规则由订单实体自己把控order.cancel(userId);orderMapper.updateById(order);// 调用其他域的服务如库存...}这么做有什么好处第一业务逻辑不再到处泄漏。关于“订单能不能取消”的规则被死死锁在Order类里。任何人想取消订单都必须调用order.cancel()这就保证了状态流转的合法性。想绕过校验直接把状态改成 2门都没有。第二代码真正变得可测试。在贫血模型下你想测一段订单取消逻辑得把整个 Spring 容器起起来Mock 掉一堆 Mapper。现在你只需要new一个Order对象调用cancel()方法看看状态变没变、异常抛没抛就行了。这才是纯粹的单元测试。最后回答开头面试官的问题。实体类里全是 Get/Set那就是披着对象外衣的数据表。把业务行为内聚到实体里让实体自己保护自己的状态你才算真正用上了面向对象编程这也是跨入领域驱动设计门槛的第一步。
返回列表