在TypeScript的同一Builder模式中,如何对创建(不需要ID)和更新(必须有ID)实现有条件的运行时校验?
注:下面我用一个Permission实体作为一个简单、通用的示例来说明结构性问题。
我在Node.js(TypeScript)后端实现Builder模式,在把对象传递给仓储层之前对它进行实例化。 我需要这个Builder能在运行时根据是“Create”还是“Update”操作动态地强制执行以下验证规则:
- 创建时:id属性缺失。name和 module这两个字段是严格必填的。
- 更新时:id必须严格提供。然而,name和 module变为可选,前提是至少提供其中一个。未提供的字段必须保持未定义,以便仓储层知道不覆盖数据库中的相应列。
以下是我当前的实现,它目前未能支持字段的可选性以及对id的条件性要求:
import { CustomError } from "../../../shared";
export class PermissionBuilder {
private id?: string;
private name?: string;
private module?: string;
constructor() {}
setId(id: string): PermissionBuilder {
if (!id || !id.trim()) throw CustomError.badRequest('ID cannot be empty');
this.id = id;
return this;
}
setName(name: string): PermissionBuilder {
if (!name || !name.trim()) throw CustomError.badRequest('Name cannot be empty');
this.name = name;
return this;
}
setModule(module: string): PermissionBuilder {
if (!module || !module.trim()) throw CustomError.badRequest('Module cannot be empty');
this.module = module;
return this;
}
// How to implement the runtime validation logic here?
build() {
// TODO: Enforce mandatory name/module if ID is missing (Create)
// TODO: Enforce mandatory ID and at least one field if ID is present (Update)
return {
id: this.id,
name: this.name,
module: this.module,
};
}
}
技术要求: * 给出对build() 方法(或内部状态)的具体重构,确保在不产生碎片化、无效对象状态的前提下完成这些条件性的运行时检查。 * 解释在build() 阶段Builder如何安全地区分这两种状态,或说明该模式是否本质上需要拆分成两个独立的构建方法(例如buildForCreate() 与buildForUpdate())以处理互斥的运行时约束。
解决方案
我会把它拆成两个Builder。
创建与更新之间的验证规则完全不同:
- 创建需要
name和module - 更新需要
id,但name和module变为可选
在同一个Builder中处理两种流程通常会让 build() 充满条件判断,导致返回类型尴尬。
像下面这样会更清晰:
class CreatePermissionBuilder {
private name?: string;
private module?: string;
setName(name: string) {
if (!name.trim()) {
throw CustomError.badRequest("Name is required");
}
this.name = name;
return this;
}
setModule(module: string) {
if (!module.trim()) {
throw CustomError.badRequest("Module is required");
}
this.module = module;
return this;
}
build() {
if (!this.name || !this.module) {
throw CustomError.badRequest("Missing required fields");
}
return {
name: this.name,
module: this.module,
};
}
}
class UpdatePermissionBuilder {
private id?: string;
private name?: string;
private module?: string;
setId(id: string) {
if (!id.trim()) {
throw CustomError.badRequest("Id is required");
}
this.id = id;
return this;
}
setName(name?: string) {
this.name = name;
return this;
}
setModule(module?: string) {
this.module = module;
return this;
}
build() {
if (!this.id) {
throw CustomError.badRequest("Id is required");
}
if (this.name == null && this.module == null) {
throw CustomError.badRequest(
"At least one field must be provided"
);
}
return {
id: this.id,
...(this.name !== undefined && { name: this.name }),
...(this.module !== undefined && { module: this.module }),
};
}
}
在更新构建器中使用 undefined 字段也让仓储层决定实际应更新哪些列。如果重复开始增多,你可以后续提取共享验证,但我不会尝试把两种用例强行放在一个Builder中。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。