继承和静态类成员 - 在实例化之前,构造函数/类的类型是什么?

时间:2017-08-13 15:58:53

标签: class typescript inheritance types static

完全做作的示例传入 - 它是解释发生什么的最简单方法。

我想我可能会在错误的层面抽象完全,让我知道!

我有一个许多其他类实现的抽象类。扩展抽象类的每个类都有多个可以使用的标识符,因此我希望每个类都用静态成员封装它们的标识符,而不是创建一个带有许多可能值的映射,而不是创建一个映射到一个类的映射。

我有一组实现一些抽象类Dog的类(CatHamsterPet)。它们包含在实用程序类PetIdentifier中。此抽象类Pet具有带签名isNameOk(name: string)的静态方法。然后扩展Pet类的每个类都有一个AcceptableNames的静态列表。在静态方法isNameOk中,我希望能够检查该名称是否在该类的可接受名称列表中。然后我希望能够传递给PetIdentifier,一个名字,然后找回我可以实例化的可能的宠物类的集合。注意:在完成过滤之前,我不想实例化。

class PetIdentifier {
  constructor (private pets: Array<The Pet Class/Constructor Type> = [Dog, Cat, Hamster]) {}
  getPossiblePets (name: string) : Array<The Pet Class/Constructor Type> {
    return this.pets.filter((pet) => pet.isNameOk(name))
  }
}

abstract class Pet {
  private static names: Array<string>
  constructor () {}
  static isNameOk (name: string) : boolean {
    return this.names.indexOf(name) > -1
  }
}

class Dog extends Pet {
  names = ['rex', 'fluffy', 'odin']
}
class Cat extends Pet {
  names = ['fluffy', 'garfield', 'socks']
}
class Hamster extends Pet {
  names = ['thor', 'odin', 'loki']
}

const petIdentifier = new PetIdentifier()
const possiblePetTypes = petIdentifier.getPossiblePets('fluffy')
// instantiate them from here onward and use them
possiblePetTypes.forEach() 

1 个答案:

答案 0 :(得分:3)

在我们开始之前,您应该为DogCatHamster添加方法或一些区别特征(例如bark() Dogmeow()的{​​{1}}和Cat的...... spin(),以便TypeScript编译器可以区分它们structurally

您可以使用Hamster来引用Pet类的静态部分,因此上述代码将基本上使用typeof Pet代替typeof Pet进行编译。遗憾的是,您无法在The Pet Class/Constructor Type调用中对其进行实例化,因为forEach()类是抽象的:

Pet

所以下一步是描述possiblePetTypes.forEach(x => new x()); // error, can't do that 的可构造子类的静态方面:

Pet

现在您将type PetConstructor = { new(): Pet; isNameOk(name: string): boolean; } 替换为The Pet Class/Constructor Type,您就会发现可以实例化它:

PetConstructor

完成,对吗?也许。你所知道的是possiblePetTypes.forEach(x => new x()); // works fine 产生了一个possiblePetTypes子类数组,但你不知道它们可能是哪一个。

您可以像这样使PetPetConstructor通用:

PetIdentifier

(请注意,我在type PetConstructor<P extends Pet> = { new(): P; isNameOk(name: string): boolean; } class PetIdentifier<PC extends PetConstructor<{}>> { constructor (private pets: Array<PC>) {} getPossiblePets (name: string) : Array<PC> { return this.pets.filter((pet) => pet.isNameOk(name)) } } 的构造函数中删除了[Dog, Cat, Hamster]的默认值,因为我们可能希望通用PetIdentifier成为其他类型的PC。我们如果我们需要,可以解决这个问题。

Pet

现在您知道const petIdentifier = new PetIdentifier([Dog, Cat, Hamster]) const possiblePetTypes = petIdentifier.getPossiblePets('fluffy') possiblePetTypes.forEach(x => new x()); // created Pet is Dog | Cat | Hamster possiblePetTypesDogCat。做完了吧?也许,但听起来你想知道在编译时 Hamster包含possiblePetTypesDog不是 {{1 },因为Cat不是Hamster的可能名称。

好吧,我不知道这样做的好方法。 TypeScript的控制流分析在任何地方都不够复杂,无法实现'fluffy'更窄,而静态类型系统并不像您需要的那样功能齐全轻松表达它。我想使用conditional mapped types,以便TypeScript可以通过检查HamsterpossiblePetTypesDog | Cat的类型来开始构建Dog.names。但它似乎还没有出现。

这就是我现在能做的事情。希望能帮助到你;祝你好运!

更新

自从我写这篇文章以来,我一直在使用类型系统,并且有一些用于推断正确类型的工作。您可能感兴趣,或者您可能认为它有点过分。这是新代码:

首先,一些辅助函数和类型:

Cat.names

现在进行繁重的工作。我将所有静态方法/属性更改为实例方法/属性。这可能不是您想要的,但以这种方式操作类型要容易得多。如果您喜欢这种方法并且仍然需要静态方法,那么您可以这样做,但它会更加繁琐。

Hamster.names

请注意,我添加了一个type Lit = string | number | boolean | undefined | null | void | {}; function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit, J extends Lit, K extends Lit, L extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L): [A, B, C, D, E, F, G, H, I, J, K, L]; function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit, J extends Lit, K extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K): [A, B, C, D, E, F, G, H, I, J, K]; function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit, J extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J): [A, B, C, D, E, F, G, H, I, J]; function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I): [A, B, C, D, E, F, G, H, I]; function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H): [A, B, C, D, E, F, G, H]; function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G): [A, B, C, D, E, F, G]; function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F): [A, B, C, D, E, F]; function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit>(a: A, b: B, c: C, d: D, e: E): [A, B, C, D, E]; function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit>(a: A, b: B, c: C, d: D): [A, B, C, D]; function tuple<A extends Lit, B extends Lit, C extends Lit>(a: A, b: B, c: C): [A, B, C]; function tuple<A extends Lit, B extends Lit>(a: A, b: B): [A, B]; function tuple<A extends Lit>(a: A): [A]; function tuple(...args: any[]): any[] { return args; } type Constructor<T> = { new(...args: any[]): T; readonly prototype: T; } type Diff<T extends string, U extends string> = ({[K in T]: K} & {[K in U]: never} & { [K: string]: never })[T]; 属性,该属性在运行时只是null。 TypeScript使用它来维护从每个可能的宠物名称到宠物类型的映射。

abstract class Pet {
  names: string[];
  constructor() {}
  isNameOk(name: string): boolean {
    return this.names.indexOf(name) > -1;
  }
  ["constructor"]: Constructor<this>;
  nameMap: Record<string, Pet> &
    Record<this['names'][number], this> &
    Record<Diff<AllPets['names'][number], this['names'][number]>, never> = null!;
    // type helper, just null at runtime
}

现在nameMap只返回一个只有相关宠物类型的数组。

class PetIdentifier<P extends Pet> {
  constructor (private pets: Array<P>) {}
  getPossiblePets<N extends string>(name: N) : Array<P['nameMap'][N]> {
    return this.pets.filter((pet) => pet.isNameOk(name))
  }
}

请注意,我使用getPossiblePets()辅助函数来声明class Dog extends Pet { names = tuple('rex', 'fluffy', 'odin') // use tuple for literals bark() { } } class Cat extends Pet { names = tuple('fluffy', 'garfield', 'socks') meow() { } } class Hamster extends Pet { names = tuple('thor', 'odin', 'loki') spin() { } } type AllPets = Dog | Cat | Hamster; // need an AllPets type 属性;这允许将tuple()推断为字符串文字的元组而不是字符串数组。 TypeScript需要字符串文字来进行映射。

另请注意,我需要names类型,它是names所有已声明子类的显式并集。这需要在AllPets的{​​{1}}属性中,因此,Pet知道名称,例如nameMap,并且可以回答&#34;否& #34;当被问及Pet是否可以命名为Cat时。 (不,没有办法设置它,以便thor可以对任何它不能识别的名称说“不”#。

最后,结果:

Cat

了解thor不再包含Cat的方式。

如果你给它一个完全随机的名字,比如:

const petIdentifier = new PetIdentifier([new Dog(), new Cat(), new Hamster()]);
const possiblePetTypes = petIdentifier.getPossiblePets('fluffy')
// possible pet types is (Dog | Cat)[], no Hamster! 

它将返回通用possiblePetTypes类型。不,我无法让它返回Hamster之类的内容,抱歉。

好的,那种作品并且完全是疯狂的,任何人都可能无法维持。我想我建议只是放弃在设计时知道const impossiblePetTypes = petIdentifier.getPossiblePets('galactus'); // impossiblePetTypes is Pet[] 来自Pet[]的{​​{1}}类型,并且认为这是一种更好处理的事实。在运行时。如果传入一个在设计时未知其值的字符串:

never[]

然后你从所有这种类型的杂耍中获得零利益。是的。将上述内容视为有趣的转移,但我不愿意尝试将其用于任何您关心部署到某种生产系统的事情。祝你好运!