




本文探讨 php 中构建器模式与不可变性的本质矛盾,指出纯不可变构建器在实践中不可行,并提供兼顾可读性、灵活性与合理不变性的实用方案——通过返回新实例而非修改自身来实现逻辑不可变。
在面向对象设计中,“构建器模式”(Builder Pattern)常用于简化复杂对象的创建过程,尤其适用于参数众多、部分可选的场景;而“不可变性”(Immutability)则强调对象一旦构造完成,其状态便不可更改。乍看之下,二者目标冲突:构建器需逐步设置属性,而不可变对象禁止任何状态变更。
但关键在于——不可变性应作用于最终产物,而非构建过程本身。真正的不可变构建器并非要求 Builder 实例自身不可变(这会使其无法链式调用),而是确保每次调用如 withName() 或 withId() 都返回一个全新的、状态确定的新构建器实例,原实例保持不变。这才是符合语义的“逻辑不可变”(value-level immutability)。
自 PHP 8.2 起,readonly 属性配合构造器参数提升(constructor property promotion)和只读数组

id, $this->email, $this->roles);
}
public function withId(int $id): self
{
return new self($this->name, $id, $this->email, $this->roles);
}
public function withEmail(?string $email): self
{
return new self($this->name, $this->id, $email, $this->roles);
}
public function withRole(string $role): self
{
$roles = [...$this->roles, $role];
return new self($this->name, $this->id, $this->email, $roles);
}
public function build(): array
{
return [
'name' => $this->name,
'id' => $this->id,
'email' => $this->email,
'roles' => $this->roles,
];
}
}
// 使用示例:全程无副作用,每次调用都产生新实例
$user = (new UserBuilder())
->withName('Alice')
->withId(42)
->withEmail('alice@example.com')
->withRole('user')
->withRole('admin')
->build();
print_r($user);
// 输出:
// Array (
// [name] => Alice
// [id] => 42
// [email] => alice@example.com
// [roles] => Array ([0] => user, [1] => admin)
// )