TypeScript构造函数重载

207次阅读
没有评论

我们探索在TypeScript中实现多个构造函数的几种不同方式。

虽然从技术上讲TypeScript仅允许一个构造函数实现,但我们可以提供多个路径来进行对象初始化。

考虑以下接口:

interface Point {
    coordinates(): Iterable<number>;
}

在探索这些不同的替代方案时,我们将提供具体的实现方式:

  1. 多种类型签名。
  2. 参数的单独接口。
  3. 静态工厂方法。
  4. 具有不同构造函数的代理类。
  5. 单一构造函数。
  6. 几种方法的结合。
  7. 重构代码。

1.多种类型的签名

class NDPoint implements Point {
    private values: number[];

    constructor()
    constructor(point: Point)
    constructor(x: number)
    constructor(x: number, y: number)
    constructor(x: number, y: number, z: number)
    constructor(...coordinates: number[])
    constructor(coordinates: number[])
    constructor(xOrPoint?: Point | number | number[], y?: number, z?: number) {
        if (typeof xOrPoint === 'undefined' || xOrPoint === null) {
            this.values = [];
        } else if (xOrPoint instanceof Array) {
            this.values = xOrPoint;
        } else if (typeof xOrPoint === 'number') {
            if (typeof y !== 'undefined') {
                if (typeof z !== 'undefined') {
                    this.values = [xOrPoint, y, z];
                } else {
                    this.values = [xOrPoint, y];
                }
            } else {
                this.values = [xOrPoint];
            }
        } else {
            this.values = [...xOrPoint.coordinates()];
        }
    }
    coordinates(): Iterable<number> {
        return this.values;
    }
}

我知道这个例子有些复杂,可以简化不同的类型签名,但是为了争论起见,请耐心等待。

第一个构造函数声明只是为了我们的利益。它们仅用于设计时,不能执行任何操作。实际上只有最后一个版本会被编译。

要创建新实例:

new NDPoint();
new NDPoint(new NDPoint());
new NDPoint(10);
new NDPoint(10, 10);
new NDPoint(10, 10, 10);
new NDPoint(10, 10, 10, 10);
new NDPoint([10, 10, 10]);

优点

  • 调用者很容易看到不同的构造函数重载。

缺点

  • 构造函数中的逻辑过多。
  • 类型检查。

2.独立的参数接口

相反,请考虑是否将构造函数参数移到其自己的类型中:

interface PointArguments {
    point?: Point;
    coordinates?: number[];
    x?: number;
    xy?: { x: number, y: number };
    xyz?: { x: number, y: number, z: number };
}

我们的实现如下所示:

class NDPoint implements Point {
    private values: number[];

    constructor(args?: PointArguments) {
        if (!args) {
            this.values = [];
        } else if (args.point) {
            this.values = [...args.point.coordinates()];
        } else if (args.coordinates) {
            this.values = args.coordinates;
        } else if (typeof args.x !== 'undefined') {
            this.values = [args.x];
        } else if (args.xy) {
            this.values = [args.xy.x, args.xy.y];
        } else if (args.xyz) {
            this.values = [args.xyz.x, args.xyz.y, args.xyz.z];
        } else {
            this.values = [];
        }
    }
    coordinates(): Iterable<number> {
        return this.values;
    }
}

要创建新实例:

new NDPoint();
new NDPoint({ point: new NDPoint() });
new NDPoint({ coordinates: [10, 10, 10] });
new NDPoint({ x: 10 });
new NDPoint({ xy: { x: 10, y: 10 } });
new NDPoint({ xyz: { x: 10, y: 10, z: 10 } });
// new NDPoint(10, 10, 10); // error, not possible

但是,如果我们这样做,会发生什么呢?

// unclear what should happen here
new NDPoint({
    coordinates: [10, 10, 10],
    x: 20,
    xyz: { x: 30, y: 30, z: 30 }
});

优点

  • 构造器逻辑变得更简单(仅需要检查成员是否存在)。

缺点

  • 模棱两可的参数接口(使调用者难以使用该类)。
  • 仍然在构造函数中包含逻辑。

3.静态工厂方法

class NDPoint implements Point {
    private values: number[];

    static fromVoid() {
        return new NDPoint([]);
    }
    static fromPoint(point: Point) {
        return new NDPoint([...point.coordinates()]);
    }
    static fromX(x: number) {
        return new NDPoint([x]);
    }
    static fromXY(x: number, y: number) {
        return new NDPoint([x, y]);
    }
    static fromXYZ(x: number, y: number, z: number) {
        return new NDPoint([x, y, z]);
    }
    static fromSpread(...coordinates: number[]) {
        return new NDPoint(coordinates);
    }
    constructor(coordinates: number[]) {
        this.values = coordinates;
    }
    coordinates(): Iterable<number> {
        return this.values;
    }
}

创建实例:

NDPoint.fromVoid();
NDPoint.fromPoint(new NDPoint([]));
NDPoint.fromX(10);
NDPoint.fromXY(10, 10);
NDPoint.fromXYZ(10, 10, 10);
NDPoint.fromSpread(10, 10, 10, 10);
new NDPoint([10, 10, 10]);

优点

  • 构造函数中没有逻辑。

缺点

  • 使用静态方法。
  • 调用者必须搜索静态方法以进行初始化。

4.具有不同构造函数的代理类

如果我们有多个实现,而不仅仅是一个实现,而每个实现都有一个不同的构造函数实现,那该怎么办呢?

class NDPoint implements Point {
    private values: number[];

    constructor(coordinates: number[]) {
        this.values = coordinates;
    }
    coordinates(): Iterable<number> {
        return this.values;
    }
}
class OneDPoint implements Point {
    private point: Point;

    constructor(x: number) {
        this.point = new NDPoint([x]);
    }
    coordinates(): Iterable<number> {
        return this.point.coordinates();
    }
}
class TwoDPoint implements Point {
    private point: Point;

    constructor(x: number, y: number) {
        this.point = new NDPoint([x, y]);
    }
    coordinates(): Iterable<number> {
        return this.point.coordinates();
    }
}
class ThreeDPoint implements Point {
    private point: Point;

    constructor(x: number, y: number, z: number) {
        this.point = new NDPoint([x, y, z]);
    }
    coordinates(): Iterable<number> {
        return this.point.coordinates();
    }
}
class PointFromSpread implements Point {
    private point: Point;

    constructor(...coordinates: number[]) {
        this.point = new NDPoint(coordinates);
    }
    coordinates(): Iterable<number> {
        return this.point.coordinates();
    }
}
class EmptyPoint implements Point {
    constructor() {
    }
    coordinates(): Iterable<number> {
        return [];
    }
}
class ClonedPoint implements Point {
    private point: Point;

    constructor(point: Point) {
        this.point = new NDPoint([...point.coordinates()]);
    }
    coordinates(): Iterable<number> {
        return this.point.coordinates();
    }
}

要创建新实例:

new EmptyPoint();
new ClonedPoint(new EmptyPoint());
new OneDPoint(10);
new TwoDPoint(10, 10);
new ThreeDPoint(10, 10, 10);
new PointFromSpread(10, 10, 10, 10);
new NDPoint([10, 10, 10]);

优点

  • 构造函数中没有逻辑。
  • 没有静态方法。

缺点

  • 非常冗长(许多代理类)。
  • 很难发现变化。

5.单一构造函数

class NDPoint implements Point {
    private values: number[];

    constructor(coordinates: number[]) {
        this.values = coordinates;
    }
    coordinates(): Iterable<number> {
        return this.values;
    }
}

要创建新实例:

new NDPoint([]);
new NDPoint([...new NDPoint([]).coordinates()]);
new NDPoint([10]);
new NDPoint([10, 10]);
new NDPoint([10, 10, 10]);
new NDPoint([10, 10, 10, 10]);

优点

  • 构造函数中没有逻辑。
  • 没有静态方法。
  • 最小的实现。

缺点

  • 负担调用者来转换构造函数输入。
  • 用于参数转换的重复代码(例如,每次要克隆一个点时重复相同的代码)。

6.几种方法的结合

class NDPoint implements Point {
    private values: number[];

    static from(coordinates: Iterable<number>) {
        return new NDPoint(...coordinates);
    }
    constructor(...coordinates: number[]) {
        this.values = coordinates;
    }
    coordinates(): Iterable<number> {
        return this.values;
    }
}

要创建新实例:

new NDPoint();
NDPoint.from(new NDPoint().coordinates());
new NDPoint(10);
new NDPoint(10, 10);
new NDPoint(10, 10, 10);
new NDPoint(10, 10, 10, 10);
NDPoint.from([10, 10, 10]);

优点

  • 构造函数中没有逻辑。

缺点

  • 使用静态方法。
  • 调用者发现静态方法和转换输入的负担很小。

7.重构代码

另一种方法是将代码重构,以便将主类作为初始化的单个点,并且我们为边缘情况提供代理类,为参数转换提供实用程序类。

class NDPoint implements Point {
    private values: Iterable<number>;

    constructor(coordinates: Iterable<number>) {
        this.values = coordinates;
    }
    coordinates(): Iterable<number> {
        return this.values;
    }
}
class EmptyPoint implements Point {
    coordinates(): Iterable<number> {
        return [];
    }
}
class IterableOf<T> implements Iterable<T> {
    private items: T[];

    constructor(...items: T[]) {
        this.items = items;
    }
    [Symbol.iterator](): Iterator<T> {
        return this.items.values();
    }
}

要创建新实例:

new EmptyPoint();
new NDPoint(new NDPoint([10, 10]).coordinates());
new NDPoint(new IterableOf(10));
new NDPoint(new IterableOf(10, 10));
new NDPoint(new IterableOf(10, 10, 10));
new NDPoint(new IterableOf(10, 10, 10, 10));
new NDPoint([10, 10, 10]);

优点

  • 构造函数中没有逻辑。
  • 没有静态方法。
  • 最可重用的代码。

缺点

  • 负担给调用者以找到实例化的方法。

概括

我发现构造函数重载是TypeScript中的一个痛点。您要么具有非常冗长的构造函数,要么需要使用静态方法或其他类来提供不同的初始化选项。