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

TypeScript中建模类型层级结构的最实用方案

TypeScript中抽象基类与多层子类型多态模型的最佳实现

问题背景

我需要构建一套包含抽象基类型、多层嵌套子类型的结构——部分子类型本身也是抽象类型,同时要定义多态函数:其中一部分是必须由子类实现的抽象函数,另一部分带有默认实现且允许子类重载。

在JavaScript中我用Class实现了一个示例(仅关注结构,无需在意语义):

class Polygon {
    constructor( name ) { this.name = name; }
    // 抽象方法(运行时断言)
    area() { assert.fail(`${this.constructor.name} must implement area()`); }
    // 带默认实现的方法
    toString() {
        const props = Object.entries(this)
            .filter( ([k, v])=>k !== 'name' )
            .map( ([k, v])=>`${k}:${v}` )
            .join(", ");
        return `${this.name}{${props}}`;
    }
}
class Triangle extends Polygon {
    constructor( base, height ) {
        super('TRIANGLE');
        this.base = base;
        this.height = height;
    }
    area() { return this.base*this.height/2; }
}
class Quadrilateral extends Polygon {
    constructor( name, base, height ) {
        super(name);
        this.base = base;
        this.height = height;
    }
    area() { return this.base*this.height; }
}
class Square extends Quadrilateral {
    constructor( side ) {
        super( 'SQUARE', side, side );
    }
    area() { return this.base*this.height; }
    toString() { return `${this.name}{side:${this.base}}`; }
}

但直接转成TypeScript会遇到两个核心问题:

  1. name字段无法作为Polygon的合法类型判别式,必须定义type PolygonType = Triangle | Square这类联合类型来替代基类做未知子类型的类型约束;
  2. Class的表达能力受限,无法利用TypeScript类型层面的元编程来定义更精确的类型结构。

如果改用TypeScript的type建模,又要放弃原型继承的多态性和Class的其他优势,因此想了解更合适的解决方案及社区推崇的实践。


可行解决方案及分析

方案1:TypeScript原生抽象类(改进版)

TypeScript原生支持abstract关键字,可以直接定义抽象基类、抽象方法,既保留Class的继承优势,又解决了JavaScript版本中抽象方法只能靠运行时断言的问题。

代码示例

abstract class Polygon {
  readonly name: string;
  // 添加字面量类型的判别式,解决类型守卫问题
  readonly type: 'triangle' | 'quadrilateral' | 'square';

  constructor(type: typeof Polygon.prototype.type, name: string) {
    this.type = type;
    this.name = name;
  }

  // 抽象方法:强制子类实现,编译时即可检测
  abstract area(): number;

  // 带默认实现的方法,子类可重载
  toString(): string {
    const props = Object.entries(this)
      .filter(([k]) => k !== 'name' && k !== 'type')
      .map(([k, v]) => `${k}:${v}`)
      .join(', ');
    return `${this.name}{${props}}`;
  }
}

class Triangle extends Polygon {
  base: number;
  height: number;

  constructor(base: number, height: number) {
    super('triangle', 'TRIANGLE');
    this.base = base;
    this.height = height;
  }

  area(): number {
    return this.base * this.height / 2;
  }
}

// 中间层抽象子类型
abstract class Quadrilateral extends Polygon {
  base: number;
  height: number;

  constructor(type: 'quadrilateral' | 'square', name: string, base: number, height: number) {
    super(type, name);
    this.base = base;
    this.height = height;
  }
}

class Square extends Quadrilateral {
  constructor(side: number) {
    super('square', 'SQUARE', side, side);
  }

  area(): number {
    return this.base * this.height;
  }

  // 重载默认方法
  toString(): string {
    return `${this.name}{side:${this.base}}`;
  }
}

核心优势

  • 编译时强制子类实现抽象方法,避免运行时错误;
  • 保留原型继承的多态性,方法仅在原型上定义一次,内存效率高;
  • 通过添加字面量类型的type字段,可直接作为合法的类型判别式,支持类型守卫和联合类型操作;
  • 支持多层抽象子类型的层级结构。

方案2:类型别名+函数多态(纯类型层面建模)

如果需要更强的类型元编程能力(比如复杂的类型变换、组合),可以用TypeScript的类型别名、接口结合函数式编程来模拟多态,同时用函数实现默认逻辑。

代码示例

// 基础结构约束
type PolygonBase = {
  name: string;
  type: 'triangle' | 'square' | 'quadrilateral';
};

// 抽象方法的类型约束
type HasArea = {
  area: () => number;
};

// 默认toString函数实现
function defaultToString<T extends PolygonBase>(obj: T): string {
  const props = Object.entries(obj)
    .filter(([k]) => k !== 'name' && k !== 'type')
    .map(([k, v]) => `${k}:${v}`)
    .join(', ');
  return `${obj.name}{${props}}`;
}

// 子类型定义
type Triangle = PolygonBase & HasArea & {
  base: number;
  height: number;
  type: 'triangle';
};

type Square = PolygonBase & HasArea & {
  side: number;
  type: 'square';
};

// 构造函数:封装实例创建与方法绑定
function createTriangle(base: number, height: number): Triangle & { toString: () => string } {
  const triangle: Triangle = {
    name: 'TRIANGLE',
    type: 'triangle',
    base,
    height,
    area: () => base * height / 2,
  };
  return {
    ...triangle,
    toString: () => defaultToString(triangle),
  };
}

function createSquare(side: number): Square & { toString: () => string } {
  const square: Square = {
    name: 'SQUARE',
    type: 'square',
    side,
    area: () => side * side,
  };
  return {
    ...square,
    toString: () => `${square.name}{side:${square.side}}`,
  };
}

核心优势

  • 完全利用TypeScript的类型系统,支持复杂的类型变换(如Omit、Pick、泛型组合等);
  • 判别式字段type天然合法,直接支持联合类型的类型守卫;
  • 实例结构完全由类型定义约束,灵活性更高。

劣势

  • 放弃了原型继承,每个实例的方法都是独立绑定的,内存开销更大;
  • 无法使用instanceof做类型判断,只能依赖判别式或类型守卫。

方案3:混合式方案(Class+类型别名/接口)

结合前两种方案的优势,用Class实现运行时的继承和多态,同时用类型别名或接口增强类型层面的表达能力,兼顾运行时效率和类型灵活性。

代码示例

// 接口:用于类型层面的约束与元编程
interface PolygonInterface {
  name: string;
  type: string;
  area(): number;
  toString(): string;
}

// 抽象基类:实现运行时逻辑
abstract class Polygon implements PolygonInterface {
  readonly name: string;
  readonly type: 'triangle' | 'quadrilateral' | 'square';

  constructor(type: typeof Polygon.prototype.type, name: string) {
    this.type = type;
    this.name = name;
  }

  abstract area(): number;

  toString(): string {
    // 默认实现
    const props = Object.entries(this)
      .filter(([k]) => k !== 'name' && k !== 'type')
      .map(([k, v]) => `${k}:${v}`)
      .join(', ');
    return `${this.name}{${props}}`;
  }
}

// 联合类型:用于约束未知子类型
type PolygonType = Triangle | Square;

class Triangle extends Polygon {
  base: number;
  height: number;

  constructor(base: number, height: number) {
    super('triangle', 'TRIANGLE');
    this.base = base;
    this.height = height;
  }

  area(): number {
    return this.base * this.height / 2;
  }
}

// 类型元编程示例:提取子类型的非公共属性
type PolygonProps<T extends PolygonType> = Omit<T, keyof PolygonInterface>;
type TriangleProps = PolygonProps<Triangle>; // { base: number; height: number }

核心优势

  • 保留Class的原型继承和运行时多态性,内存效率高;
  • 用接口/类型别名实现复杂的类型操作,满足元编程需求;
  • 可通过联合类型PolygonType实现未知子类型的类型约束,同时利用type字段做类型守卫。

方案推荐

  • 优先选择改进版抽象类方案:如果你的核心需求是运行时多态性、代码复用,且类型元编程需求不复杂,这是TypeScript社区最常用、最符合面向对象范式的方案;
  • 选择纯类型层面方案:如果核心需求是复杂的类型变换、元编程,且可以接受放弃原型继承的一些优势;
  • 选择混合式方案:如果需要同时兼顾运行时效率和类型灵活性。

内容的提问来源于stack exchange,提问作者Blue Nebula

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 23:05:55