子类类的父类类型保护

时间:2017-09-28 01:25:31

标签: typescript inheritance types parent-child instanceof

我有一个抽象类FooObject。只有两个孩子继承FooObject,我们称他们为ThisFooObjectThatFooObject

我正在寻找一种高性能方式将FooObject变成任何一个孩子。在这种情况下,这意味着不使用instanceof - performs的布尔查找速度<10%。

我调查的方式是这样的:

FooObject.ts

import ThisObject from "./ThisObject";
import ThatObject from "./ThatObject";

abstract class FooObject {

  isThisObject() : this is ThisFooObject {
    // Either check for ThisFooObject properties, or override this
    // in the ThisFooObject class to always return true.
    return duckTypingTest();
  }

  // ditto here
  isThatObject() : this is ThatFooObject {}
}

我明白这不是很好。如果我有更多FooObject的孩子,它不是一种可扩展的方法 - 并且它要求父母以有点封装方式了解这些孩子。但就我的具体情况而言,如果它避免了我必须做的事情,我愿意忍受这个。

从好的方面来说,当我有一个FooObject的引用时,我想要一个像ThatFooObject的东西时,我可以这样做:

fooObjects.forEach( foo => {
  // Foo currently typed as a FooObject
  if(foo.isThatObject()){
    // Foo now typed as a ThatFooObject
    foo.magic+=10
  }
});

这一切都有效 - 只需要一点注意事项 - 我必须be careful才能使导入顺序正确。 IE,在我的main.ts入口点,执行类似

的操作
import 'ThatFooObject';
import 'FooObject';

但除此之外,我对这种模式非常满意。当我与多个孩子一起尝试时会遇到这个问题。我开始遇到循环依赖关系丢失的问题,以及class extends value undefined is not a constructor or null之类的错误。

我无法弄清楚如何使这项工作。我只是咆哮着错误的树,我应该这样做一个更简单的方法吗?

2 个答案:

答案 0 :(得分:2)

这样的事情怎么样,这不需要课程彼此了解:

计划是给每个具体的构造函数一个名为typeId的字符串属性,我们将用它来区分子类。这是FooObject构造函数的形状:

interface FooObjectConstructor<T extends FooObject> {
  new(...args: any[]): T
  typeId: string;
}

现在我们可以定义抽象FooObject类:

abstract class FooObject {

  // note 1
  // abstract static readonly typeId: string;  

  // note 2
  readonly typeId = (this.constructor as FooObjectConstructor<this>).typeId; 

  // note 3
  instanceOf<T extends FooObject>(ctor: FooObjectConstructor<T>): this is T {
    return this.typeId === ctor.typeId
  }
}
  1. TypeScript不允许抽象静态属性,所以我不能说abstract static typeId并让编译器警告我,如果我的子类没有实现它。所以我们必须记住自己做。

  2. 我们会在创建时将静态typeId复制到每个实例,以便实例可以访问this.typeId而不是this.constructor.typeId。我不确定这是否有助于提高性能,但如果您愿意,可以将其保留在this.typeIdthis.constructor.typeId的所有地方。

  3. instanceOf方法是通用的,接受FooObject构造函数,并将当前对象的typeId与传入构造函数的typeId进行比较。事实上它是通用的,它帮助我们让父类不需要知道每个子类,并避免上面遇到的疯狂网络。

  4. 好的,让我们创建那些具体的子类。我们必须记住将静态typeId设置为唯一值:

    class ThisObject extends FooObject {
      static readonly typeId = "ThisObject" // don't forget this
      thisMethod() {
        console.log("This!")
      }      
    }
    
    class ThatObject extends FooObject {
      static readonly typeId = "ThatObject" // don't forget this
      thatMethod() {
        console.log("That!")
      }
    }
    

    好的,是时候测试一下了!

    declare const someFooObject: FooObject;
    
    if (someFooObject.instanceOf(ThisObject)) {
      someFooObject.thisMethod();
    } else if (someFooObject.instanceOf(ThatObject)) {
      someFooObject.thatMethod();
    } else {
      // some other type of FooObject, I guess
      console.log("I wasn't expecting that");
    }
    

    上面的所有代码都没有为我生成编译警告,而works for me at runtime

    希望有所帮助;祝你好运!

答案 1 :(得分:0)

好的,我找到了 解决方案 - 但它并不好。

通过在父母之前导入子类,在main.ts中强制执行正确的导入顺序,看看我必须做的舞蹈:

main.ts

import "./ThisFooObject"; // <-- forces correct order.
import FooObject from "./FooObject";

对于所有其他孩子,我必须加倍努力,让所有孩子都这样做。由于我只有两个,这不是一个很大的问题,但这会随着更多的子类呈指数级增长。 EG:

ThisFooObject.ts

import "./ThatFooObject" <-- Forces correct order despite not being used
import "./FooObject"

ThatFooObject.ts

import "./ThisFooObject" <-- Forces correct order despite not being used
import "./FooObject"