简体   繁体   English

Inheritance 在 typescript 中具有类型限制

[英]Inheritance with type restriction in typescript

what I'm trying to implement:我正在尝试实现的内容:

the high level abstract class called Unit containing most part of logic shared between all his descendants.高级抽象 class 称为 Unit 包含其所有后代之间共享的大部分逻辑。 Each descendant has own implementation of 'act' method with own type to return.每个后代都有自己的“act”方法实现,并有自己的返回类型。 The top (highest level) abstract class has interface for a method act, this method can return any of types that descendant can create, so type in declaration his type very dynamic.顶层(最高级别)抽象 class 具有方法行为的接口,该方法可以返回后代可以创建的任何类型,因此在声明中键入他的类型非常动态。 But each descendant can work only with own type, so i don't want to copy/paste abstract method declaration, because of two reasons:但是每个后代只能使用自己的类型,所以我不想复制/粘贴抽象方法声明,原因有两个:

  1. It seems redundant.似乎是多余的。
  2. When in real class I implement logic based on abstract descendant of top Unit class, TS allows me to return anything top class has in his declaration.当在真正的 class 中,我基于顶级单元 class 的抽象后代实现逻辑时,TS 允许我返回 class 在其声明中的任何顶部。 But I want TS to restrict implementation with exactly type is declared in descendant of a Unit class.但是我希望 TS 限制在 Unit class 的后代中声明的确切类型的实现。

The problem问题

When I try to make declaration inside the descendant that is stricter than type in parent, TS saying my type is not compatible当我尝试在后代中进行比父类型更严格的声明时,TS 说我的类型不兼容

Playground Link 游乐场链接

Code from example:示例代码:

// declare some enum of types I need for abstract classes
enum UnitActions {
  run = 'run',
  fly = 'fly',
  dive = 'dive',
}

// declare different structure of results that my abstract (not a top one) classes should return
type UnitRunResult = { distance: number; time: number };
type UnitFlyResult = { distance: number; vector: { x: number; y: number } };
type UnitSwimResult = { distance: number; depth: number };

// dynamic types for results based on enum
type UnitActionResults<T extends UnitActions> = T extends UnitActions.run
  ? UnitRunResult
  : T extends UnitActions.fly
  ? UnitFlyResult
  : T extends UnitActions.dive
  ? UnitSwimResult
  : never;

// main abstract class, contains a lot of high-level logic, omitted for readability
abstract class Unit {
  public abstract actionType: UnitActions;
  // abstract method that define only common idea
  public abstract act<T extends UnitActions>(): UnitActionResults<T>;
}

// abstract class that implements basic logic for some exactly kind of units
abstract class WoodenUnit extends Unit {
  public actionType = UnitActions.run;
  // on this level I already know that my wooden units can only Run.
  // is there any way to declare only one possible kind of returning result?
  public abstract act<T extends UnitActions>(): UnitActionResults<T>;

  // I've tried to strictly define T as UnitActions.run - it doesn't work
  // public abstract act<T extends UnitActions.run>(): UnitRunResult;

  // the same idea as above but with different syntax - doesn't work either
  // public abstract act<T = UnitActions.run>(): UnitRunResult;
}

class Pinokio extends WoodenUnit {
  // on this level I want types to be nested from abstract class,
  // because there will be a lot of such Units and copying the same type again and again seems redundant
  public act() {
    return { distance: 20, time: 3 };
  }
}

// some very high-level code that works with Units by their common interface
function test(unit: Unit) {
  switch (unit.actionType) {
    case UnitActions.run: {
      const result = unit.act();
      // do something with results
      // in this place I'm sure what exactly type I use and wanna to get proper type from TS;
      return;
    }
    case UnitActions.fly: {
      // etc..
    }
  }
  return;
}

Maybe my idea with using actionType field to separate logic is wrong and becase of it whole my idea is wrong.也许我使用 actionType 字段来分隔逻辑的想法是错误的,因为我的想法是错误的。 In that case how it could be achieved in another way?在那种情况下,如何以另一种方式实现?

Looks to me like you have one act() method that does one of three different and completely unrelated things.在我看来,您有一个act()方法可以执行三种不同且完全不相关的事情之一。 So consider having three different methods: run() , fly() and swim() instead of act() .所以考虑使用三种不同的方法: run()fly()swim()而不是act() You can put these on different interfaces:您可以将它们放在不同的接口上:

interface RunUnit {
  actionType: UnitActions.run;
  run(): UnitRunResult;
}

interface FlyUnit {
  actionType: UnitActions.fly;
  fly(): UnitFlyResult;
}

interface SwimUnit {
  actionType: UnitActions.swim;
  swim(): UnitSwimResult;
}

Notice the specific types of the actionType fields above;注意上面actionType字段的具体类型; they're not simply actionType: UnitActions but actually allow only one particular value from that enum.它们不仅仅是actionType: UnitActions ,而且实际上只允许该枚举中的一个特定值。 This lets you define a discriminated union to distinguish them at runtime:这使您可以定义一个有区别的联合以在运行时区分它们:

type ActUnit = RunUnit | FlyUnit | SwimUnit

These actions are now completely decoupled from the Unit class:这些操作现在与Unit class 完全分离:

abstract class Unit {
}

Implementing units doesn't need a lot of boilerplate now:实施单元现在不需要很多样板:

abstract class WoodenUnit extends Unit implements RunUnit {
    actionType = UnitActions.run as const;
    abstract run(): UnitRunResult;
}

class Pinokio extends WoodenUnit {
  public run(): UnitRunResult {
    return { distance: 20, time: 3 };
  }
}

Finally, let's call some of these methods.最后,让我们调用其中的一些方法。 We don't care about the Unit class in this signature at all.我们根本不关心此签名中的Unit class。 All we care about is that it can act, ie that it's an ActUnit :我们关心的是它是否可以行动,即它是一个ActUnit

function test(unit: ActUnit) {
  switch (unit.actionType) {
    case UnitActions.run: {
      // We can now call run() because the type has been narrowed to RunUnit!
      const result = unit.run();
      // do something with results
      return;
    }
    case UnitActions.fly: {
      // etc..
    }
  }
  return;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM