用不同的项目重构相同的功能

时间:2019-06-11 07:14:08

标签: typescript refactoring

我有以下

  public static carthographicLerp(v1: Cartographic, v2: Cartographic, t: number): Cartographic {
    Math.min(Math.max(t, 0), 1);
    const result = new Cesium.Cartographic();
    result.longitude = v1.longitude + (v2.longitude - v1.longitude) * t;
    result.latitude = v1.latitude + (v2.latitude - v1.latitude) * t;
    result.height = v1.height + (v2.height - v1.height) * t;
    return result;
  }

  public static cartesianLerp(v1: Cartesian3, v2: Cartesian3, t: number): Cartesian3 {
    Math.min(Math.max(t, 0), 1);
    const result = new Cesium.Cartesian3();
    result.x = v1.x + (v2.x - v1.x) * t;
    result.y = v1.y + (v2.y - v1.y) * t;
    result.z = v1.z + (v2.z - v1.z) * t;
    return result;
  }

  public static headingPitchRollLerp(v1: HeadingPitchRoll, v2: HeadingPitchRoll, t: number): HeadingPitchRoll {
    Math.min(Math.max(t, 0), 1);
    const result = new Cesium.HeadingPitchRoll();
    result.heading = v1.heading + (v2.heading - v1.heading) * t;
    result.pitch = v1.pitch + (v2.pitch - v1.pitch) * t;
    result.roll = v1.roll + (v2.roll - v1.roll) * t;
    return result;
  }

代码完全相同,但是对象不同。

是否可以由此创建一个函数,而不是使它丑陋并简单地称为lerp?

1 个答案:

答案 0 :(得分:1)

我在任何地方都没有您的Cesium库,因此我假设我们具有以下最低限度的定义集:

interface Cartographic {
  longitude: number;
  latitude: number;
  height: number;
}
interface Cartesian3 {
  x: number;
  y: number;
  z: number;
}
interface HeadingPitchRoll {
  heading: number;
  pitch: number;
  roll: number;
}
const Cesium = {
  Cartographic: ((() => ({})) as any) as new () => Cartographic,
  Cartesian3: ((() => ({})) as any) as new () => Cartesian3,
  HeadingPitchRoll: ((() => ({})) as any) as new () => HeadingPitchRoll
};

这是将*Lerp()函数泛化为单个curried函数的一种方法,该函数采用构造函数和一组键并返回专用于这些类型的函数:

type CesiumTypes = Cartographic | Cartesian3 | HeadingPitchRoll;

const genericLerp = <K extends keyof T, T extends Record<K, number>>(
  ctor: new () => T,
  keys: K[]
) => (v1: T, v2: T, t: number): T => {
  Math.min(Math.max(t, 0), 1);
  const result = new ctor();
  for (let k of keys) {
    (result[k] as number) = v1[k] + (v2[k] - v1[k]) * t; // assertion here
  }
  return result;
};

请注意,我必须在result[t] as number = ...中使用type assertion。那是因为作业在技术上是不安全的。如果类型T的键K上的数字属性比数字更窄,例如{foo: -1 | 0 | 1},那么做{{1 }},因为无法保证这种计算会产生result.foo = v1.foo + (v2.foo - v1.foo) * t类型。我只是假设您在调用-1 | 0 | 1时不会使用类似的类型。如果有人愿意,他们可以加强genericLerp()来防止被这种病理类型的人叫醒,但这可能不值得。

无论如何,现在您可以通过调用genericLerp()来编写原始的三个函数:

genericLerp()

那可能是我想去的地方。制作像所有这些功能的 single 函数比较困难。在编译时,您可以制作一个与三个单独函数的所有签名匹配的overloaded函数,但是您仍然需要在运行时检查这些值以确定要使用这三个函数中的哪个(即,哪个构造函数和哪个键集)。实施测试无处可逃,这就是为什么我认为这可能不值得。为了完整起见,这是一个可能的方法。

首先让我们创建一些type guard functions,以向编译器展示我们打算采用的值并检查其所属的具体类型。我将通过检查某些键的存在来做到这一点:

const carthographicLerp = genericLerp(Cesium.Cartographic, [
  "latitude",
  "longitude",
  "height"
]);

const cartesianLerp = genericLerp(Cesium.Cartesian3, ["x", "y", "z"]);

const headingPitchRollLerp = genericLerp(Cesium.HeadingPitchRoll, [
  "heading",
  "pitch",
  "roll"
]);

现在我们可以制作一个重载函数,该函数检查值并将其分派给其他实现:

// Type guards to figure out what the value is
function isCartographic(x: CesiumTypes): x is Cartographic {
  return "longitude" in x;
}
function isCartesian3(x: CesiumTypes): x is Cartesian3 {
  return "x" in x;
}
function isHeadingPitchRoll(x: CesiumTypes): x is HeadingPitchRoll {
  return "heading" in x;
}

好的,希望能有所帮助。祝你好运!

Link to code

相关问题