订单建造者模式应聚焦构造过程的可靠性:必填校验放build()、setter立即转对象并返回$this、避免重复校验与副作用、编号生成仅在build()执行一次,且不混用Factory。

订单对象字段多、校验逻辑分散,直接 new Order() 容易漏参数或绕过业务规则
PHP 里订单这种实体通常有几十个字段,还带状态流转、金额计算、地址合法性校验等逻辑。硬写 new Order($a, $b, $c, ...) 不仅难维护,还容易传错顺序、漏必填项,更别说后续加字段要改所有调用点。
建造者模式的核心不是“炫技”,是把构造过程拆成可复用、可验证、可组合的步骤。重点不是“怎么写 Builder 类”,而是“怎么让 Order 的创建不依赖调用方的记忆力和责任心”。
- 必须把必填字段校验放在
build()里,而不是构造函数里——否则 builder 过程中无法灵活跳过某些非关键步骤(比如测试时临时跳过支付校验) - 不要让
OrderBuilder持有原始数组或未加工的数据,比如别存$this->rawAddress = $arr,而应立刻转成Address对象,避免 build 前校验失效 - 如果订单要支持“草稿→提交→支付成功”多阶段构建,
build()应返回不同状态的Order实例,而不是一个通用对象
PHP 中 builder 方法链式调用失效,常见于没正确 return $this
很多人写完 setCustomer() 忘记 return $this,结果 $builder->setCustomer(...)->setItems(...) 报错说 Call to a member function setItems() on null。这不是语法问题,是 PHP 对方法链的显式要求。
更隐蔽的问题是:在 setter 里做了类型转换或默认值填充后,忘了 return $this。比如把字符串金额转成 float 后直接 return,就断了链。
- 每个 setter 必须以
return $this;结尾,不能只写$this->customer = $customer; - 如果某个 setter 要做轻量校验(如邮箱格式),校验失败应 throw 异常,而不是静默 return $this —— 否则错误被吞掉,调用方以为成功了
- 避免在 setter 里做重操作(如查数据库、发 HTTP 请求),builder 应保持无副作用;这类逻辑留到
build()阶段统一处理
build() 方法里重复校验 or 状态污染,导致订单对象不可靠
有人习惯在每个 setter 里都校验一遍字段,结果同一个邮箱被校验三次;也有人在 build() 里又重新解析一遍地址字符串,而之前 setter 已经解析过并存了 Address 对象——这不仅浪费 CPU,还可能因两次解析逻辑不一致导致数据矛盾。
关键是分清职责:setter 负责“接收并预处理”,build 负责“终验并冻结”。比如地址字段,setter 应直接接受 Address 对象,而不是字符串;金额字段,setter 应转成 float 或 Money 对象并存好,build 只检查是否为空、是否为正数。
-
build()不该再调用filter_var($this->email, FILTER_VALIDATE_EMAIL)—— 如果 email 是字符串,这个动作应该在setEmail()里完成并转成规范格式 - 避免在
build()里修改已设置的属性(如自动补created_at),而应提前在 setter 或构造器里设默认值;否则 builder 实例复用时会残留旧时间戳 - 如果订单需生成唯一编号(如
ORD-20240521-XXXXX),编号生成必须在build()最后一刻做,且只能做一次;重复调用build()不应生成新编号
Laravel 项目里混用工厂模式和建造者,结果对象状态混乱
很多人在 Laravel 里看到 Order::factory()->create() 就以为它能替代建造者,其实完全不是一回事。Factory 是为测试造数据,不走业务校验、不触发事件、不调用 domain logic;而 OrderBuilder 是生产环境里构造真实订单的入口。
更麻烦的是,有人把 factory 和 builder 做成同一套配置,比如用 OrderFactory 的 state() 方法去模拟 builder 的链式调用,结果测试能过,线上一提交就抛出“收货人手机号为空”——因为 factory 没跑 build() 里的终验逻辑。
- 不要让
OrderBuilder依赖OrderFactory,也不要反过来;二者目标不同,混用等于把测试逻辑塞进业务流 - 如果要用 factory 辅助测试 builder,只应在测试代码里手动 new Builder → set → build → assert,而不是试图让 factory “模拟” builder 行为
- 在 Laravel Service Provider 里注册 builder 时,别用
bind(OrderBuilder::class, ...)绑定单例——builder 实例是一次性的,复用会导致字段污染
事情说清了就结束。最常被忽略的一点是:builder 不是给所有实体用的,只有当对象构造涉及三步以上不可省略的校验、或字段之间存在强依赖(比如设置了优惠券就必须校验用户等级)、或需要支持部分构建(如先存草稿再补发票信息)时,才值得上 builder。否则,一个带命名参数的构造函数加几个静态工厂方法,更简单可靠。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











