关键在于用抽象基类或接口定义Builder(如abstract class PizzaBuilder),声明抽象构建方法,由具体子类实现,Product应不可变且通过Build()一次性构造,避免Builder持有其引用。

Builder 类怎么写才不和 Product 耦合
关键不是“写个 Builder”,而是让 Builder 不依赖具体 Product 的构造逻辑。否则每次改产品结构,就得同步改 Builder,失去模式意义。
常见错误是直接在 Builder 里 new Product() 或暴露 Product 的字段赋值——这等于把创建细节全塞进 Builder,违背封装原则。
- 用抽象基类或接口定义
Builder,比如abstract class PizzaBuilder,只声明BuildDough()、BuildTopping()等抽象方法 - 让具体
Builder(如NYStylePizzaBuilder)自己决定如何构建每一步,返回this实现链式调用 -
Product应该是不可变的,或只提供私有构造 + 公共只读属性;所有字段由Builder在Build()中一次性传入构造函数 - 避免在
Builder中持有Product实例引用;推荐在Build()方法里 new 出最终对象
Director 类真的必须存在吗
很多教程硬加一个 Director 类来封装构建流程,但实际项目里它往往成了多余的一层——尤其当构建步骤固定、且只有一两种组合时。
典型误用:为一个只有三步的配置对象,硬套 Director.Construct(),结果调用方还得先 new Director,再传 Builder,再调 Construct,反而更啰嗦。
- 如果构建逻辑简单(比如 “先设 URL,再设超时,最后设重试次数”),直接在业务代码里链式调用
builder.SetUrl("...").SetTimeout(30).SetRetry(2).Build()更清晰 - 只有当同一组步骤被多处复用,且顺序/条件有差异(比如“测试环境跳过认证,生产环境强制开启”),才值得抽出
Director -
Director内部不应知道具体Builder类型,只依赖抽象Builder接口
为什么 Fluent Builder 经常崩在 null 引用
链式调用看着优雅,但 SetXxx() 返回 this 后,如果某步初始化失败(比如解析 JSON 配置出错),后续调用就可能触发 NullReferenceException,而错误堆栈根本看不出哪步断了。
这不是语法问题,是状态管理缺失。Fluent 接口默认假设每步都成功,但现实里网络、IO、校验都可能失败。
- 每个
SetXxx()方法里做参数校验,非法输入立刻 throw,别默默吞掉 - 不要在
Builder字段未初始化完就允许调用Build();可在Build()开头检查必要字段是否已设置,缺一不可 - 考虑用
Result或Option包装Build()结果(需引入第三方库如LanguageExt),或者至少抛出带上下文的异常,比如new InvalidOperationException("Missing required field: ConnectionString")
C# 12 的 Primary Constructor 对 Builder 有什么影响
有了 public class Pizza(string dough, string sauce, string topping) 这种主构造函数,很多人以为 Builder 可以省了——其实反而更容易写出脆弱代码。
主构造函数让 Pizza 看似“一行搞定”,但一旦字段增多、校验变复杂(比如 sauce 必须是枚举值、dough 长度不能超 20 字),直接 new 就变成高风险操作。
- Primary Constructor 适合 DTO 或纯数据容器;复杂对象仍需 Builder 控制构建过程
- 可以把主构造设为
private,只供Builder内部调用,对外完全隐藏构造细节 - 别为了用新语法,把
Builder简化成一堆 public set 属性——那只是把耦合从构造函数挪到了属性上
真正难的不是写出 Builder 结构,而是判断哪些字段该由 Builder 控制、哪些该在 Build 时校验、哪些错误该提前暴露。这些边界没想清楚,模式就变成套壳麻烦。










