typescript 类型作为可重写的类属性

c9qzyr3d  于 2023-04-13  发布在  TypeScript
关注(0)|答案(1)|浏览(173)

我想为继承创建一个非常通用的类,但我遇到了一些问题。
到目前为止,我有两个服务类(称为Service_A和Service_B),它们用于相同的目的,除了 Service_AClass_A 一起工作,Service_BClass_B 一起工作。这些类非常相似,现在,只有提到的类型(Class_A和Class_B)在它们之间有所不同。
我想做的是在一个更通用的类(EntityService)中编写一次代码,Service_A和Service_B将从这个类继承。
然而,我还没有设法找到一种方法,让Entity的方法使用Service_A和Service_B将覆盖的“泛型”类/类型,以便所有代码都可以转移,而不必覆盖所有方法或在每个方法调用时指定类型。
例如,EntityService看起来是这样的:

declare class Observable<T> { x: T }

declare class Actor {
    doStuff<T>(anything: T): T[];
    getData<I>(): I;
    getObservable<U, I>(data: I): Observable<U[]>;
}

interface Entity {
    param_1: number,
    param_2: number,
}

class EntityObj {
    id: string;
    param_1: number;
    param_2: number;

    constructor(id: string, param_1: number, param_2: number) {
        this.id = id;
        this.param_1 = param_1;
        this.param_2 = param_2;
    }

    static fromInterface(entity: Entity, id: string): EntityObj {
            return new this(id, entity.param_1, entity.param_2);
    }
}

export class EntityService {

    /* What I'd like to be able to use:
     * static classTypeAlias: EntityObj;
     * static interfaceTypeAlias: Entity;
     */

    constructor(private readonly actor: Actor) { }

    getEntityObjs(): Observable<EntityObj[]> | null {
        let data: Entity = this.actor.getData()
        let entityObjs: Observable<EntityObj[]> = this.actor.getObservable<EntityObj, Entity>(data);
        return entityObjs;
    }

    getObjFromInterface(): EntityObj {
        let entityObj: EntityObj = EntityObj.fromInterface(this.actor.getData(), "foo");
        return entityObj;
    }

    update(entity: EntityObj): void { let entityDoc = this.actor.doStuff<Entity>(entity); }
}

让Service_A能够调用update()之类的方法,其参数隐式地为“Class_A”或getEntityObjs()类型,并且隐式地返回“Observable〈Class_A[]〉”类型的内容|空”
我试着查看泛型类型,但在尝试访问属性时,如果不从EntityObj扩展它们,就无法使用它们,或者使用与类关联的泛型类型的值(如class EntityService<classType, interfaceType> {...})来指定args / returns的类型。
任何指向任何一种解决方案/适当的技术将是非常受欢迎的

tsm1rwdh

tsm1rwdh1#

如果您希望能够参数化EntityService的子类,以便它们具有不同的类型,您将调用classTypeAliasinterfaceTypeAlias,最直接的方法是在对应于这些类型的类型参数中创建EntityClassgeneric;我们分别将它们命名为CI

export class EntityService<C, I> {
  ⋯
}

因此,首先要做的是将EntityObj的每个示例都替换为C,并将Entity替换为I

export class EntityService<C, I> {

  constructor(private readonly actor: Actor) { }

  getEntityObjs(): Observable<C[]> | null {
    let data: I = this.actor.getData()
    let entityObjs: Observable<C[]> = this.actor.getObservable<C, I>(data);
    return entityObjs;
  }

  getObjFromInterface(): C {
    let entityObj: C = C.fromInterface(this.actor.getData(), "foo"); // error
    // --------------> ~
    // 'C' only refers to a type, but is being used as a value here.(2693)
    return entityObj;
  }

  update(entity: C): void {
    let entityDoc = this.actor.doStuff<I>(entity); // error
    // ---------------------------------> ~~~~~~
    // Argument of type 'C' is not assignable to parameter of type 'I'.
  }

}

除了两个错误之外,这基本上是可行的。
下面的错误发生是因为我们调用doStuff<I>(entity),但是传递了一个C类型的entity而不是I。这可以通过约束C扩展I来解决,但是如果你不想这样做,我们可以改变它,这样我们就可以调用doStuff<C>(entity)

update(entity: C): void {
  let entityDoc = this.actor.doStuff<C>(entity); // okay
}

最上面的错误发生是因为我们用C替换了EntityObj * 类构造函数 *,C只是一个 * 类型 *。糟糕,这意味着EntityService需要访问适当类型的类构造函数值。由于代码使用了fromInterface(data, str),其中data的类型为I,并且它返回了C类型的值,然后我们需要访问一个{fromInterface(data: I, str: string): C}类型的值。我们怎么才能得到它呢?好吧,一个合理的地方是,当你构造EntityService的示例时,需要传递这样一个对象。这导致我们得到这个版本:

export class EntityService<C, I> {

  constructor(
    private readonly actor: Actor,
    public ctor: { fromInterface(e: I, id: string): C } // <-- add it
  ) { }

  getEntityObjs(): Observable<C[]> | null {
    let data: I = this.actor.getData<I>()
    let entityObjs: Observable<C[]> = this.actor.getObservable<C, I>(data);
    return entityObjs;
  }

  getObjFromInterface(): C {
    let entityObj: C = this.ctor.fromInterface(this.actor.getData(), "foo");
    // --------------> ^^^^^^^^^ <--- use it
    return entityObj;
  }

  update(entity: C): void { let entityDoc = this.actor.doStuff<C>(entity); }
}

现在可以编译了。那么我们怎么用它呢?
好吧,我们可以用特定的CI类型参数来子类化它,并在子类构造函数中传递ctor。像这样:

class MainEntityService extends EntityService<EntityObj, Entity> {
  constructor(actor: Actor) {
    super(actor, EntityObj);
  }
}

所以C被指定为EntityObjI被指定为Entity,而ctor对象只是EntityObj构造函数。我们可以使用它:

new MainEntityService(new Actor()).getEntityObjs()
// Observable<EntityObj[]> | null

看起来不错。如果有关系,这里有一个不同的子类:

interface Foo {
  a: string;
  b: number;
}
class Bar implements Foo {
  a = "abc";
  b = 123;
  c = "🤷‍♂️";
  static fromInterface(entity: Foo, id: string): Bar {
    return new this();
  }
}

class SomeOtherThing extends EntityService<Bar, Foo> {
  constructor(actor: Actor) { super(actor, Bar) }
}
new SomeOtherThing(new Actor()).getEntityObjs()
// Observable<Bar[]> | null

看起来也不错。
毫无疑问,还有其他方法可以实现这一点,例如子类不需要显式指定CI,或者EntityService是一个类工厂函数,您只需编写class MainEntityService extends EntityService(EntityObj) {},等等,但这种方法有效,所以我不会离题,以包括每一种可能的方法。
Playground链接到代码

相关问题