You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TypeScript中模拟密封抽象类实现穷尽性检查?

在TypeScript中模拟Kotlin密封类的穷尽性检查

Kotlin的密封类通过限制子类范围,让编译器能做穷尽性分支检查。在TypeScript中,我们可以通过几种方式实现类似效果,避免新增子类时遗漏分支处理。

问题背景

你的Expr类继承体系中,新增Neg子类后,TypeScript不会自动提示Add#rightAssoc中的instanceof检查不完整,可能导致未处理分支抛出运行时错误。以下是几种解决思路:


方案1:使用区分联合类型(推荐)

区分联合类型是TypeScript实现密封逻辑的原生方案,通过唯一的判别属性(如type字段)让编译器识别所有可能的类型分支,强制进行穷尽性检查。

// 定义Expr为所有表达式类型的联合
type Expr = NumExpr | AddExpr | NegExpr;

// 每个表达式类型包含唯一的type判别符
interface NumExpr {
  type: "num";
  value: number;
}

interface AddExpr {
  type: "add";
  left: Expr;
  right: Expr;
}

interface NegExpr {
  type: "neg";
  inner: Expr;
}

// 实现eval方法
function evalExpr(expr: Expr): number {
  switch (expr.type) {
    case "num": return expr.value;
    case "add": return evalExpr(expr.left) + evalExpr(expr.right);
    case "neg": return -evalExpr(expr.inner);
    // 新增类型时,这里会提示缺少case分支
    default:
      // 用never类型确保所有分支被覆盖
      const _exhaustiveCheck: never = expr;
      throw new Error(`Unexpected expression type: ${_exhaustiveCheck}`);
  }
}

// 实现rightAssoc方法
function rightAssoc(expr: Expr): Expr {
  switch (expr.type) {
    case "num": return expr;
    case "add": {
      const leftAssoc = rightAssoc(expr.left);
      // 递归处理左操作数的右结合
      if (leftAssoc.type === "add") {
        return rightAssoc({
          type: "add",
          left: leftAssoc.left,
          right: { type: "add", left: leftAssoc.right, right: expr.right }
        });
      }
      return {
        type: "add",
        left: leftAssoc,
        right: rightAssoc(expr.right)
      };
    }
    case "neg": return {
      type: "neg",
      inner: rightAssoc(expr.inner)
    };
    default:
      const _exhaustiveCheck: never = expr;
      throw new Error(`Unexpected expression type: ${_exhaustiveCheck}`);
  }
}

优势:

  • 原生支持穷尽性检查,新增类型时编译器自动报错
  • 代码更简洁,避免类继承的冗余
  • 类型推导更精准

方案2:限制类继承范围 + 联合类型

如果需要保留类的面向对象结构,可以通过私有构造函数限制子类只能在同一文件中定义,同时将Expr类型声明为所有子类的联合,实现编译时检查。

// 将Expr定义为所有子类的联合类型
type Expr = Num | Add | Neg;

// 抽象基类,私有构造函数限制外部继承
abstract class ExprBase {
  private constructor() {}

  abstract eval(): number;
  abstract rightAssoc(): Expr;

  // 允许子类调用构造函数
  protected static create<T extends ExprBase>(this: new (...args: any[]) => T, ...args: any[]): T {
    return new this(...args);
  }
}

class Num extends ExprBase {
  constructor(public value: number) {
    super();
  }

  override eval(): number {
    return this.value;
  }

  override rightAssoc(): Num {
    return new Num(this.value);
  }
}

class Add extends ExprBase {
  constructor(public left: Expr, public right: Expr) {
    super();
  }

  override eval(): number {
    return this.left.eval() + this.right.eval();
  }

  override rightAssoc(): Expr {
    const leftAssoc = this.left.rightAssoc();
    if (leftAssoc instanceof Num) {
      return new Add(leftAssoc, this.right.rightAssoc());
    } else if (leftAssoc instanceof Add) {
      return new Add(leftAssoc.left, new Add(leftAssoc.right, this.right).rightAssoc());
    } else if (leftAssoc instanceof Neg) {
      // 新增Neg后必须处理,否则编译器报错
      return new Add(leftAssoc, this.right.rightAssoc());
    } else {
      // never类型确保穷尽性
      const _exhaustiveCheck: never = leftAssoc;
      throw new Error(`Unexpected expression type: ${_exhaustiveCheck}`);
    }
  }
}

class Neg extends ExprBase {
  constructor(public inner: Expr) {
    super();
  }

  override eval(): number {
    return -this.inner.eval();
  }

  override rightAssoc(): Neg {
    return new Neg(this.inner.rightAssoc());
  }
}

优势:

  • 保留类的封装和继承特性
  • 私有构造函数防止外部新增子类
  • 联合类型确保编译时穷尽性检查

方案3:穷尽性检查辅助函数

如果不想修改现有类结构,可以添加一个辅助函数,利用never类型在编译时检测未覆盖的分支。

// 辅助函数:接收never类型参数,确保所有分支被覆盖
function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);
}

// 在Add的rightAssoc中使用
class Add extends Expr<number> {
  // ...其他代码...

  public override rightAssoc(): Expr<number> {
    const expr = this.left.rightAssoc();
    if (expr instanceof Num) {
      return new Add(expr, this.right.rightAssoc());
    } else if (expr instanceof Add) {
      return new Add(expr.left, new Add(expr.right, this.right).rightAssoc());
    } else if (expr instanceof Neg) {
      // 新增Neg后必须添加此分支,否则assertNever报错
      return new Add(expr, this.right.rightAssoc());
    }
    // 如果有未处理的分支,expr不是never类型,这里编译报错
    return assertNever(expr);
  }
}

优势:

  • 最小化修改现有代码
  • 编译时检测遗漏分支
  • 实现简单,无需重构

内容的提问来源于stack exchange,提问作者Aadit M Shah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:10:32