如何禁止Fluent Builder Interface中可选字段方法重复调用
解决Fluent Builder可选方法重复调用问题
核心思路是通过拆分阶段接口,在编译层面限制已调用的可选方法无法重复调用,同时配合运行时校验防止绕过编译限制。以下是具体实现方案(以C#为例):
1. 定义阶段接口
拆分不同的可选字段阶段,每个阶段仅暴露未被调用过的方法:
// 第一步:必填FirstName,返回Prefix选择阶段 public interface IFirstNameSelectionStage { IPrefixSelectionStage WithFirstName(string firstName); } // 第二步:必填Prefix,进入可选字段初始阶段 public interface IPrefixSelectionStage { IOptionalFieldsInitialStage WithPrefix(string prefix); } // 可选字段初始阶段:可调用WithMiddleName或WithLastName public interface IOptionalFieldsInitialStage : IFacultyNumberSelectionStage { IOptionalFieldsWithoutMiddleNameStage WithMiddleName(string middleName); IOptionalFieldsWithoutLastNameStage WithLastName(string lastName); } // 已调用WithMiddleName后:仅能调用WithLastName和必填FacultyNumber public interface IOptionalFieldsWithoutMiddleNameStage : IFacultyNumberSelectionStage { IOptionalFieldsWithoutBothStage WithLastName(string lastName); } // 已调用WithLastName后:仅能调用WithMiddleName和必填FacultyNumber public interface IOptionalFieldsWithoutLastNameStage : IFacultyNumberSelectionStage { IOptionalFieldsWithoutBothStage WithMiddleName(string middleName); } // 两个可选字段都已调用:仅能调用必填FacultyNumber public interface IOptionalFieldsWithoutBothStage : IFacultyNumberSelectionStage { } // 必填FacultyNumber阶段:填完后进入完成阶段 public interface IFacultyNumberSelectionStage { IBuilderCompletionStage WithFacultyNumber(string facultyNumber); } // 最终完成阶段:创建实例 public interface IBuilderCompletionStage { Person Build(); }
2. 实现Builder类
实现所有阶段接口,同时添加运行时校验防止强制类型转换绕过限制:
public class PersonBuilder : IFirstNameSelectionStage, IPrefixSelectionStage, IOptionalFieldsInitialStage, IOptionalFieldsWithoutMiddleNameStage, IOptionalFieldsWithoutLastNameStage, IOptionalFieldsWithoutBothStage, IFacultyNumberSelectionStage, IBuilderCompletionStage { private readonly Person _person = new Person(); public IPrefixSelectionStage WithFirstName(string firstName) { _person.FirstName = firstName; return this; } public IOptionalFieldsInitialStage WithPrefix(string prefix) { _person.Prefix = prefix; return this; } public IOptionalFieldsWithoutMiddleNameStage WithMiddleName(string middleName) { if (!string.IsNullOrEmpty(_person.MiddleName)) throw new InvalidOperationException("MiddleName已设置,不可重复赋值"); _person.MiddleName = middleName; return this; } public IOptionalFieldsWithoutLastNameStage WithLastName(string lastName) { if (!string.IsNullOrEmpty(_person.LastName)) throw new InvalidOperationException("LastName已设置,不可重复赋值"); _person.LastName = lastName; return this; } // 实现IOptionalFieldsWithoutLastNameStage的WithMiddleName方法 public IOptionalFieldsWithoutBothStage WithMiddleName(string middleName) { if (!string.IsNullOrEmpty(_person.MiddleName)) throw new InvalidOperationException("MiddleName已设置,不可重复赋值"); _person.MiddleName = middleName; return this; } // 实现IOptionalFieldsWithoutMiddleNameStage的WithLastName方法 public IOptionalFieldsWithoutBothStage WithLastName(string lastName) { if (!string.IsNullOrEmpty(_person.LastName)) throw new InvalidOperationException("LastName已设置,不可重复赋值"); _person.LastName = lastName; return this; } public IBuilderCompletionStage WithFacultyNumber(string facultyNumber) { _person.FacultyNumber = facultyNumber; return this; } public Person Build() { return _person; } } // 目标实体类 public class Person { public string FirstName { get; set; } public string Prefix { get; set; } public string FacultyNumber { get; set; } public string MiddleName { get; set; } public string LastName { get; set; } }
3. 效果验证
- 编译层面限制:调用过
WithMiddleName后,返回的接口不再包含该方法,编译器会直接报错,无法重复调用:// 错误示例:编译报错,IOptionalFieldsWithoutMiddleNameStage无WithMiddleName方法 var invalidBuilder = new PersonBuilder() .WithFirstName("John") .WithPrefix("Mr.") .WithMiddleName("Michael") .WithMiddleName("James"); - 运行时兜底:即使通过强制类型转换绕过编译限制,也会抛出异常,避免重复赋值。
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

