0

我有以下代码:

const enum ShapeType {
  Circle,
  Rectangle
}

class Shape {
  constructor(public shapeType: ShapeType) {}
}

class Circle extends Shape {
  constructor(public x: number, public y: number, public r: number) {
    super(ShapeType.Circle);
  }
}

class Rectangle extends Shape {
  constructor(public x: number, public y: number, public w: number, public h: number) {
    super(ShapeType.Rectangle);
  }
}

function handleRectangleRectangleCollision(r1: Rectangle, r2: Rectangle) {
  return Helpers.doRectanglesCollide(r1.x, r1.y, r1.w, r1.h, r2.x, r2.y, r2.w, r2.h)
}

function handleRectangleCircleCollision(r: Rectangle, c: Circle) {
  return Helpers.circleRectangleCollision(c.x, c.y, c.r, r.x, r.y, r.w, r.h);
}

function handleCircleCircleCollision(c1: Circle, c2: Circle) {
  return Helpers.circlesCollide(c1.x, c1.y, c1.r, c2.x, c2.y, c2.y);
}

function handleCircleRectangleCollision(c: Circle, r: Rectangle) {
  return Helpers.circleRectangleCollision(c.x, c.y, c.r, r.x, r.y, r.w, r.h);
}

export let colliderMapping = {
  [ShapeType.Rectangle]: {
    [ShapeType.Rectangle]: handleRectangleRectangleCollision,
    [ShapeType.Circle]: handleRectangleCircleCollision
  },
  [ShapeType.Circle]: {
    [ShapeType.Circle]: handleCircleCircleCollision,
    [ShapeType.Rectangle]: handleCircleRectangleCollision
  }
}

function doShapesCollide(s1: Shape, s2: Shape) {
  let colliderFn = colliderMapping[s1.shapeType][s2.shapeType];

  return colliderFn(s1, s2);
}

我在最后一个最后一个错误:

return colliderFn(s1, s2);

Argument of type 'Shape' is not assignable to parameter of type 'Rectangle & Circle'.
  Type 'Shape' is missing the following properties from type 'Rectangle': x, y, w, h

我明白为什么我会收到错误(我认为),但我不知道如何解决它。我基本上是想通过一个映射变量来实现一种干净的双重调度方式,这样每个形状组合都会返回一个有效的函数,我可以调用它来查看它们是否发生冲突。

有没有办法做到这一点?如果是这样,怎么做?

4

2 回答 2

0

我注意到了这一点handleRectangleCircleCollisionhandleCircleRectangleCollision做了同样的事情,但只是改变了传递参数的顺序。

function handleRectangleCircleCollision(r: Rectangle, c: Circle) {
  return Helpers.circleRectangleCollision(c.x, c.y, c.r, r.x, r.y, r.w, r.h);
}

function handleCircleRectangleCollision(c: Circle, r: Rectangle) {
  return Helpers.circleRectangleCollision(c.x, c.y, c.r, r.x, r.y, r.w, r.h);
}

您还使用类和继承(OOP 方法)与类之外的功能。

我试图减少代码并保持简单。这是一个使用类型保护的解决方案。我还用交叉类型替换了这些类。

type Point = { x: number; y: number; }
type Rectangle = { w: number; h: number; } & Point
type Circle = { r: number } & Point
type Shape = Rectangle | Circle

function isCircle(shape: Shape): shape is Circle {
  return 'r' in shape;
}  
function handleShapeCollision(shapeA: Shape, shapeB: Shape) {
  if (isCircle(shapeA)) 
  return handleCircleCollision(shapeA, shapeB);
  return handleRectangleCollision(shapeA, shapeB);
}
function handleCircleCollision(circle: Circle, shape: Shape) {
  if (isCircle(shape)) 
  return Helpers.circlesCollide(circle.x, circle.y, circle.r, shape.x, shape.y, shape.y);
  return Helpers.circleRectangleCollision(circle.x, circle.y, circle.r, shape.x, shape.y, shape.w, shape.h);
}
function handleRectangleCollision(r: Rectangle, shape: Shape) {
  if (isCircle(shape)) 
  return Helpers.circleRectangleCollision(shape.x, shape.y, shape.r, r.x, r.y, r.w, r.h);
  return Helpers.doRectanglesCollide(r.x, r.y, r.w, r.h, shape.x, shape.y, shape.w, shape.h)
}

这里是一个TypescriptPlayground。随意玩耍。

于 2021-08-28T19:25:25.847 回答
0

请看我的文章

考虑这个超级简单的例子:

type A = {
  check: (a: string) => string
}

type B = {
  check: (a: number) => number
}

type C = {
  check: (a: symbol) => number
}

type Props = A | B | C;
declare var props:Props;

props.check() // (a: never) => string | number

为什么check期望never而不是所有可能类型的联合?

因为函数参数处于逆变位置,所以它们被合并为 never,因为string & number & symbolis never;

尝试将check参数类型更改为某个对象:

type A = {
    check: (a: { a: 1 }) => string
}

type B = {
    check: (a: { b: 1 }) => number
}

type C = {
    check: (a: { c: 1 }) => number
}

type Props = A | B | C;
declare var props: Props;

//(a: { a: 1;} & { b: 1;} & { c: 1;}) => string | number
props.check()

很明显,您有所有可能的参数类型的交集。

有几种解决方法。

您可以添加条件语句:

function doShapesCollide(s1: Shape, s2: Shape) {
    if (s1.shapeType === ShapeType.Circle && s2.shapeType === ShapeType.Circle) {
        let colliderFn = colliderMapping[s1.shapeType][s2.shapeType];
        return colliderFn(s1, s2); // should be ok
    }
}

上述方法仍然会导致编译错误,因为s1is aShape并且colliderFn期望CircleCircle是的一个子类型Shape并且更具体 - 因此它不起作用。

为了使其工作,您应该添加另一个条件:


function doShapesCollide(s1: Shape | Circle, s2: Shape | Circle) {
    if (s1.shapeType === ShapeType.Circle && s2.shapeType === ShapeType.Circle) {
        let colliderFn = colliderMapping[s1.shapeType][s2.shapeType];
        if (s1 instanceof Circle && s2 instanceof Circle) {
            return colliderFn(s1, s2); // should be ok
        }
    }

}

它有效,但很丑陋。是不是?

您还可以创建多个类型保护,使代码更简洁但添加更多业务逻辑。

或者您可以将函数的并集转换为交集,换句话说,您可以产生函数重载。

const enum ShapeType {
    Circle,
    Rectangle
}

class Shape {
    constructor(public shapeType: ShapeType) { }
}

class Circle extends Shape {
    constructor(public x: number, public y: number, public r: number) {
        super(ShapeType.Circle);
    }
}

class Rectangle extends Shape {
    constructor(public x: number, public y: number, public w: number, public h: number) {
        super(ShapeType.Rectangle);
    }
}

function handleRectangleRectangleCollision(r1: Rectangle, r2: Rectangle) {
}

function handleRectangleCircleCollision(r: Rectangle, c: Circle) {
}

function handleCircleCircleCollision(c1: Circle, c2: Circle) {
}

function handleCircleRectangleCollision(c: Circle, r: Rectangle) {
}

export let colliderMapping = {
    [ShapeType.Rectangle]: {
        [ShapeType.Rectangle]: handleRectangleRectangleCollision,
        [ShapeType.Circle]: handleRectangleCircleCollision
    },
    [ShapeType.Circle]: {
        [ShapeType.Circle]: handleCircleCircleCollision,
        [ShapeType.Rectangle]: handleCircleRectangleCollision
    }
}

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;

function doShapesCollide(s1: Shape, s2: Shape) {
    let colliderFn = colliderMapping[s1.shapeType][s2.shapeType];
    type Overload =
        & UnionToIntersection<typeof colliderFn>
        & ((r1: Rectangle | Circle | Shape, r2: Rectangle | Circle | Shape) => void)
    const overloaded = colliderFn as Overload
    return overloaded(s1, s2); // should be ok
}

操场

以上更改不需要您更改业务逻辑。

于 2021-08-28T18:19:33.367 回答