如何将抽象 class 转换为非抽象 class?

How to cast abstract class to non-abstract class?

如果我有一个 abstract class Adapter {},我想创建一个函数,它接受一个非抽象构造函数扩展这个 class 和 returns class.

例如

export abstract class Adapter {}

export function change(adapterConstructor: typeof Adapter) {
  return new adapterConstructor();
}

不幸的是,这个例子不起作用,因为打字稿编译器正确地抱怨抽象 class 无法实例化。

是否可以将 adapterConstructor 参数限制为仅 typeof Adapter 的非抽象实现?

即像

export abstract class Adapter {}

export function change(adapterConstructor: NonAbstract<typeof Adapter>) {
  return new adapterConstructor();
}

目前,我的解决方案是简单地使 Adapter class 成为非抽象的,并让 应该 成为抽象的所有方法抛出错误。

您可以动态化,但存在允许传入 Adapter 本身的风险,这有点违背抽象 class...

的目的
export abstract class Adapter {}

export function change(adapterConstructor: typeof Adapter): Adapter {
    const constr: any = adapterConstructor;
    return new constr() as Adapter;
}

否则,您需要引入一个具体的基础 class 来代替 Adapter...或者将 Adapter 更改为每个 class 必须实现的接口比使用摘要 class.

是的,您可以要求 change() 方法将 typeof Adapter(未知可更新)与构造函数签名相交。 (编辑 TS4.2+:类型 typeof Adapter 现在已知有一个 abstract construct signature,所以你不能把它留在那里。相反,你可以使用 Pick<typeof Adapter, keyof typeof Adapter> 来保留任何静态属性,但完全丢弃任何 call/construct 签名)。

首先,我要给 Adapter 一些结构,因为 empty classes behave strangely in TypeScript 我们不希望它绊倒我们。此外,我想向 Adapter 添加一个静态方法,以便您可以说服自己 change() 将能够调用它:

export abstract class Adapter {
  prop = "adapterProp";
  static staticMethod() {
    console.log("HI THERE");
  }
}

所以这是 change() 签名:

export function change(
  adapterConstructor: Pick<typeof Adapter, keyof typeof Adapter> & (new () => Adapter)
) {
  adapterConstructor.staticMethod();
  return new adapterConstructor();
}

你可以看到 adapterConstructor 是可更新的并且有 Adapter 的静态方法。请注意,构造函数签名是 new () => Adapter,这意味着它不需要参数并生成 Adapter.

让我们确保它的行为如您所愿:

const cannotConstructAbstract = new Adapter(); // error!
change(Adapter); // error! cannot assign abstract to non-abstract

您不能 new Adapter class,也不能按需要调用 change(Adapter)

现在让我们做一个具体的子class:

class ChickenAdapter extends Adapter {
  cluck() {
    console.log("B'KAW!");
  }
}
    
const canConstruct = new ChickenAdapter(); // okay
const chickenAdapter = change(ChickenAdapter); // okay

可以newChickenAdapterclass,所以你可以调用change(ChickenAdapter),return ]s 一个 Adapter:

chickenAdapter.prop // okay
chickenAdapter.cluck(); // error! it only returns an Adapter, not a ChickenAdapter

请注意,chickenAdapter 不是一个 ChickenAdapter 实例,因为 change() return 是一个 Adapter。如果你想让 change() 跟踪这个,你可以在 return 类型中使其通用:

export function change<T extends Adapter>(
  adapterConstructor: typeof Adapter & (new () => T)
) {
  adapterConstructor.staticMethod();
  return new adapterConstructor();
}

然后 chickenAdapter.cluck() 有效:

chickenAdapter.cluck(); // okay

让我们继续讨论一些边缘情况:

class CowAdapter extends Adapter {
  constructor(public color: string) {
    super();
  }

  moo() {
    console.log("MRRROOOORM!");
  }
}

const howNow = new CowAdapter("brown");
const butNeedsAnArgument = new CowAdapter(); // error! expected 1 argument
change(CowAdapter); // error!  CowAdapter needs arguments

这里,change()不接受CowAdapter,因为那个class需要一个构造函数参数,change()的body没有传进去。大概是这样吧要做的事。

另外,让我们确保您不能传递静态方面看起来不错但不会产生 Adapter:

的内容
class NotAnAdapter {
  static staticMethod() {}
  notAnAdapter = true;
}

change(NotAnAdapter); // error!
//Property 'prop' is missing in type 'NotAnAdapter' but required in type 'Adapter'.

看起来也不错。


好的,希望对你有帮助;祝你好运!

Link to code