测试驱动开发中Order类实现方式的疑问:为何需改用静态工厂方法而非原有实现
我明白你现在的困惑——你已经通过TDD写出了能通过测试的Order实现,却疑惑为什么要改成静态工厂方法+不可变对象+枚举的形式,尤其是当核心需求只是表达“下单是一个意图”的时候。咱们一步步拆解来看,先分析原有实现的潜在问题,再对应看新设计的优势:
一、原有实现的核心问题(为何不符合“下单是意图”的业务语义)
你的初始实现能通过测试,但从业务模型和代码健壮性来看,存在几个和“下单是意图”不匹配的点:
可变性破坏了意图的确定性
原有Order类提供了setPlaced和setState方法,意味着任何拿到Order实例的代码都能随意修改它的状态。但“下单是意图”这个业务概念里,一旦用户提交了下单意图,这个订单的初始状态(REQUESTED、已下单)就应该是确定且不可篡改的——意图是一个一次性的、明确的动作,不应该被后续代码意外修改。实例方法
place()的语义混淆
你的place()是实例方法,但它的逻辑是创建并返回一个新的Order实例,这非常反直觉:调用一个已存在的Order对象的place(),得到另一个完全无关的订单?这和“下单是意图”的语义完全不搭——下单意图应该是创建一个新的订单实体,而不是基于已有订单生成新的。允许存在无意义的“空订单”
原有实现允许直接new Order()创建一个状态为空、placed为false的订单,但这样的对象在业务上是无效的:没有下单意图的订单根本不应该存在。这种“无效状态”的存在,会给后续代码带来潜在的bug(比如不小心使用了未调用place()的空订单)。
二、新实现为何更契合“下单是意图”的业务模型
改用静态工厂方法+不可变设计的核心,是让代码的结构直接映射业务规则,而不仅仅是通过测试:
静态工厂方法的语义直接对应业务动作
public static Order place()这个方法名和调用方式,直接告诉所有开发者:要表达“用户提交下单意图”这个动作,就调用这个方法。调用者不需要先创建一个无意义的空Order,再去调用place()——完全贴合“下单是意图”的业务概念,语义清晰到不需要额外注释。不可变性保证意图的完整性
用private final OrderState state+私有构造器,确保一旦Order实例被创建,它的状态就永远不能被修改。这完美契合“下单是意图”的特性:意图一旦产生,就是确定的、不可变更的,没有任何代码能意外篡改这个状态,从根源上避免了状态不一致的bug。封装创建逻辑,隔离业务规则
把Order的创建逻辑封装在静态工厂方法里,后续如果业务规则变化(比如下单时需要记录用户ID、下单时间),只需要修改place()方法的实现,调用者的代码完全不需要改动。比如未来需要:public static Order place(UUID userId, Instant orderTime) { return new Order(OrderState.REQUESTED, userId, orderTime); }这种设计符合开闭原则,也让“下单意图”的规则变化不会扩散到整个代码库。
彻底杜绝无效状态
私有构造器+静态工厂方法,完全禁止了创建“无意义空订单”的可能——所有Order实例都是通过place()创建的,天生就是“已提交下单意图”的有效状态。这从代码层面保证了业务规则的执行,不需要依赖调用者的自觉。
三、关于枚举的补充(结合设计的额外价值)
用OrderState枚举替代字符串,除了你已经理解的类型安全(避免打错字符串的低级错误),更重要的是把“订单状态”这个业务概念具象化了——它不再是一个随意的字符串,而是和Order的业务生命周期绑定的、明确的状态值,这让“下单意图对应REQUESTED状态”这个规则更清晰、更易维护。
总结
你的初始实现是“能通过测试的正确代码”,但新实现是贴合业务模型的健壮代码。TDD的目标从来不是只写出能通过测试的代码,而是写出能准确反映业务规则、易于维护、不易引入bug的代码。当业务复杂度提升(比如订单后续要经历支付、发货、完成等状态流转),这种不可变+静态工厂+枚举的设计优势会更加明显——它能帮你把业务规则牢牢地封装在代码结构里,而不是依赖零散的判断和注释。




