提问人:Jonas Pauthier 提问时间:11/11/2023 最后编辑:Jonas Pauthier 更新时间:11/13/2023 访问量:24
如何使用多态性通过“transformer”类继承将对象从一种类型转换为另一种类型
How to use polymorphism to transform an object from one type into another with "transformer" classes inheritance
问:
我正在开发一个 GraphQL API。GraphQL 类型和数据库类型不一样,因为我不希望我的数据库特定结构泄露给 API 使用者。基本上,我想重命名属性或从手头的对象中删除属性,具体取决于我是将数据返回给使用者还是将对象发送到数据库。GraphQL 中的类型具有属性,但在数据库端,它们具有 .id: string
_id: string, _key: string
这组转换对于所有顶级类型都是全局的,因此我想到使用一个基本的“Transformer”类来实现该共享逻辑。然后,API 中的每个域都可以有一个特定的类来扩展基类,但根据它所处理的特定类型提供更多转换。我命名了来自 API 层的类型和来自数据库的类型。Model
Entity
这是我最终得到的实现,但我不确定这是键入它的最佳方式:
// base-transformer.ts
export interface Entity {
_id: string;
_key: string;
}
export interface Model {
id: string;
}
export class BaseTransformer {
toEntity(model: Model): Entity {
const { id, ...rest } = model;
const [, key] = id.split("/");
return { ...rest, _id: id, _key: key };
}
toModel(entity: Entity): Model {
const { _id, _key, ...rest } = entity;
return { ...rest, id: _id };
}
}
// car-transformer.ts
import { BaseTransformer, type Model, type Entity } from "./base-transformer";
export interface CarModel extends Model {
plateNumber: string;
}
type CarEntity = Omit<CarModel, "id"> & Entity;
export class CarTransformer extends BaseTransformer {
toEntity(model: CarModel): CarEntity {
// that's the part that troubles me. I tried not to have to cast the return
// but obviously with polymorphism you can get to the most child type to the
// parent type but not the other way around.
return super.toEntity(model) as CarEntity;
}
toModel(entity: CarEntity): CarModel {
return super.toModel(entity) as CarModel;
}
}
我还尝试了绑定泛型类型,但后来我最终得到了泛型抽象的子类型可能与泛型扩展的类型不同。我找到的解决方案是将回调传递给其工作是将父类型转换为泛型类型的方法。我觉得这首先会破坏这个架构的目的,因为我必须做与该类相同的工作,并将其用于每个子类。这是它的样子:TS Error 2322
BaseTransformer
T
BaseTransformer
// base-transformer.ts
export interface Entity {
_id: string;
_key: string;
}
export interface Model {
id: string;
}
export class BaseTransformer {
toEntity<R extends Entity>(model: Model): R {
const { id, ...rest } = model;
const [, key] = id.split("/");
return { ...rest, _id: id, _key: key }; // TS Error 2322 here
}
toModel<R extends Model>(entity: Entity): R {
const { _id, _key, ...rest } = entity;
return { ...rest, id: _id }; // TS Error 2322 here
}
}
答:
唯一可行的方法是使基类在特定于每个子类的模型类型中泛型:M
class BaseTransformer<M extends Model> {
toEntity(model: M): ToEntity<M> {
const { id, ...rest } = model;
const [, key] = id.split("/");
return { ...rest, _id: id, _key: key };
}
toModel(entity: ToEntity<M>) {
const { _id, _key, ...rest } = entity;
return { ...rest, id: _id };
}
}
返回类型等效于(使用 Omit
实用程序类型来抑制一组键,并使用 intersection 来添加一组键),所以为了节省空间,我定义了一个实用程序类型:toEntity()
Omit<M, "id"> & Entity
ToEntity<M>
type ToEntity<M extends Model> = Omit<M, "id"> & Entity;
这意味着 的输入类型应为 。我没有注释它的返回类型,编译器推断为 .从概念上讲,它应该只是 ,但不幸的是,如果您尝试将其注释为这样,您将收到一个错误:toModel
ToEntity<M>
Omit<ToEntity<M>, "_id" | "_key"> & { id: string; }
M
toModel(entity: ToEntity<M>): M {
const { _id, _key, ...rest } = entity;
return { ...rest, id: _id }; // error!
// ^-- Type 'Omit<ToEntity<M>, "_id" | "_key"> & { id: string; }'
// is not assignable to type 'M'.
}
这是因为类型检查器无法对泛型类型的抽象不变属性执行任意高阶推理,尤其是当它们涉及 中使用的条件类型时(这取决于 Exclude
,这是一个条件类型)。是的,可能与实际情况相同(从技术上讲,它们可能不同;例如,属性可能是 like 的子类型),但类型检查器看不到它。Omit
Omit<Omit<M, "id"> & Entity, "_id" | "_key"> & { id: string }
M
id
string
"foo"
因此,有两种方法可以进行。要么你只是断言返回类型是 ,M
toModel(entity: ToEntity<M>) {
const { _id, _key, ...rest } = entity;
return { ...rest, id: _id } as Model as M;
}
或者,您可以不带注释返回类型。我选择不带注释的返回类型。在正确实现子类的情况下,这无关紧要:
interface CarModel extends Model {
plateNumber: string;
}
type CarEntity = Omit<CarModel, "id"> & Entity;
export class CarTransformer extends BaseTransformer<CarModel> {
toEntity(model: CarModel): CarEntity {
return super.toEntity(model);
}
toModel(entity: CarEntity): CarModel {
return super.toModel(entity);
}
}
但是,如果 和 的返回类型的等价性被破坏,您将收到一个错误,希望会有所帮助:M
toModel()
interface OopsieModel extends Model {
id: "foo" // <-- narrower than string
x: number
}
interface OopsieEntity extends Entity {
x: number
}
class OopsieTransformer extends BaseTransformer<OopsieModel> {
toEntity(model: OopsieModel): OopsieEntity {
return super.toEntity(model);
}
toModel(entity: OopsieEntity): OopsieModel {
return super.toModel(entity) // error!
// Type 'string' is not assignable to type '"foo"'
}
}
但这实际上取决于你想采用哪种方法的用例。
评论
toModel
BaseTransformer
: Model
toModel
toEntity
toModel()
M
Omit
Omit<ToEntity<M>, "_id" | "_key"> & { id: string; }
M
M
as M
as Model as M
toModel()