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

TypeScript中用SceneItem父类引用存储子类实例的问题求解

TypeScript光线投射模拟器:统一存储SceneItem子类对象的OOP解决方案

我正在开发一个基于TypeScript后端的光线投射模拟器,希望将场景中所有元素存储为SceneItem类型的数组——所有场景元素均间接继承自SceneItem。但尝试以下声明时:

let myMirror: SceneItem = new FlatMirror(params...);

会抛出错误:Type 'FlatMirror' is not assignable to type 'SceneItem'。如果将引用类型声明为FlatMirror,又无法将其放入SceneItem数组中。

请问有没有无需使用大型联合类型的OOP解决方案或设计模式,能统一存储场景中的对象?


现有代码片段

SceneItem.ts

export default abstract class SceneItem {
  abstract getNormal(point: number[]): number[];
  abstract intersectPoint(placeholder: Ray): number[];
  abstract newSegment(incident: Ray): RaySegment;
}

ReflectiveItem.ts

import Ray from "./Ray";
import SceneItem from "./SceneItem";
import reflect from "../functions/reflection";
import { RaySegment } from "../types/RaySegment";

export default abstract class ReflectiveItem extends SceneItem {
  abstract getNormal(point: number[]): number[];
  abstract intersectPoint(ray: Ray): number[];

  newSegment(ray: Ray): RaySegment {
    const segment = ray.segments[ray.segments.length - 1];

    let incident = segment.direction;
    let intersect = this.intersectPoint(ray);
    let normal = this.getNormal(intersect);

    const reflected: RaySegment = {
      direction: reflect(incident, normal),
      endpoints: intersect,
      intensity: segment.intensity,
      refractive: segment.refractive,
    };
    return reflected;
  }
}

FlatMirror.ts

import ReflectiveItem from "./ReflectiveItem";
import Ray from "./Ray";
import inRange from "../functions/inRange";
import intersect from "../functions/intersect";
import normalize from "../functions/normalize";

export default class FlatMirror extends ReflectiveItem {
  point1: number[];
  point2: number[];
  normal: number[];

  constructor(x1: number, y1: number, x2: number, y2: number) {
    super();

    this.point1 = [x1, y1];
    this.point2 = [x2, y2];

    this.normal = normalize([
      this.point1[1] - this.point2[1],
      this.point2[0] - this.point1[0],
    ]);
  }

  intersectPoint(ray: Ray) {
      //calculates intersect point
  }

  getNormal(point: number[]) {
    return this.normal;
  }
}

问题根源与修复方案

1. 修复子类抽象方法的实现错误

当前FlatMirror的intersectPoint方法没有返回值(仅留注释),但SceneItem要求该方法必须返回number[]类型。TypeScript会因此判定FlatMirror未正确实现父类抽象方法,导致类型不兼容。

修改FlatMirror.ts的intersectPoint,确保返回符合类型要求的结果:

intersectPoint(ray: Ray): number[] {
  // 替换为实际交点计算逻辑,返回[X,Y]格式数组
  const intersection = intersect(ray, this.point1, this.point2);
  return inRange(intersection, this.point1, this.point2) ? intersection : [];
}

2. 移除抽象子类中重复的抽象方法声明

ReflectiveItem作为SceneItem的子类,无需重复声明getNormal和intersectPoint——这两个抽象方法已从父类继承。重复声明不仅冗余,还可能因签名细节差异(如参数名不一致)导致TypeScript类型检查误判。

修改ReflectiveItem.ts,移除重复的抽象方法:

import Ray from "./Ray";
import SceneItem from "./SceneItem";
import reflect from "../functions/reflection";
import { RaySegment } from "../types/RaySegment";

export default abstract class ReflectiveItem extends SceneItem {
  // 移除重复的abstract getNormal和intersectPoint声明

  newSegment(ray: Ray): RaySegment {
    const segment = ray.segments[ray.segments.length - 1];

    let incident = segment.direction;
    let intersect = this.intersectPoint(ray);
    let normal = this.getNormal(intersect);

    const reflected: RaySegment = {
      direction: reflect(incident, normal),
      endpoints: intersect,
      intensity: segment.intensity,
      refractive: segment.refractive,
    };
    return reflected;
  }
}

3. 验证类型兼容性

修复后,以下代码将正常工作:

// 直接将FlatMirror实例赋值给SceneItem类型变量
let myMirror: SceneItem = new FlatMirror(0,0,100,100);
// 将子类实例存入SceneItem数组
const sceneItems: SceneItem[] = [myMirror, /* 其他SceneItem子类实例 */];

后续扩展的设计模式建议

如果需要新增更多场景元素类型(如折射物体、发光体等),可以考虑以下模式:

  • 工厂模式:创建SceneItemFactory类统一负责各类场景元素的实例化,避免业务代码直接使用new操作符,同时保证返回类型统一为SceneItem。
  • 访问者模式:如果需要对不同类型的SceneItem执行差异化操作(如渲染、碰撞检测),引入访问者模式可避免在父类中添加过多耦合方法,同时保留类型安全。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 02:17:05