Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: How to set method return value => subclass

Tags:

typescript

I have a parent class with two subclasses:

abstract class Point {
    public readonly x: number;
    public readonly y: number;

    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }

    diff(point: Point): Point {
        return this.update(this.x - point.x, this.y - point.y);
    }
    // many methods like diff(); and then...

    protected abstract update(x: number, y: number): Point;
}



class ImmutablePoint extends Point {
    protected update(x: number, y: number): Point {
        return new ImmutablePoint(x, y);
    }
}



class MutablePoint extends Point {
    public x: number;
    public y: number;

    protected update(x: number, y: number): Point {
        this.x = x;
        this.y = y;
        return this;
    }
}


const pointA: ImmutablePoint = new ImmutablePoint(10, 10)
const pointB: ImmutablePoint = new ImmutablePoint(6, 2);

But diff() method returns a Point, not a ImmutablePoint

// Error: type 'Point' is not assignable to parameter of type 'ImmutablePoint'.
const result: ImmutablePoint = pointA.diff(pointB);

I'm looking for a way to re-define the method signature on the subclass without writing a new implementation, is it possible?

I also tried to make diff() return value this but it doesn't work because ImmutablePoint doesn't return this but a new ImmutablePoint

Playground Link

like image 703
A. Matías Quezada Avatar asked Mar 09 '17 21:03

A. Matías Quezada


2 Answers

You can have this as a return type and you can access the constructor of your current object with this.constructor. That allows you to work more easily with subclassed immutables.

abstract class Point {
    public readonly x: number;
    public readonly y: number;

    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }

    diff(point: Point): this {
        return this.update(this.x - point.x, this.y - point.y);
    }
    // many methods like diff(); and then...

    protected abstract update(x: number, y: number): this;
}



class ImmutablePoint extends Point {
    protected update(x: number, y: number): this {
        return new (<any>this.constructor)(x, y);
    }
}



class MutablePoint extends Point {
    public x: number;
    public y: number;

    protected update(x: number, y: number): this {
        this.x = x;
        this.y = y;
        return this;
    }
}


const pointA: ImmutablePoint = new ImmutablePoint(10, 10)
const pointB: ImmutablePoint = new ImmutablePoint(6, 2);
const result: ImmutablePoint = pointA.diff(pointB);
like image 67
Will Seitz Avatar answered Sep 22 '22 00:09

Will Seitz


You can make Point generic:

abstract class Point<T extends Point<any>> {
    public readonly x: number;
    public readonly y: number;

    constructor(x: number, y: number) {
        ...
    }

    diff(point: Point<any>): T {
        return this.update(this.x - point.x, this.y - point.y);
    }

    protected abstract update(x: number, y: number): T;
}

class ImmutablePoint extends Point<ImmutablePoint> {
    protected update(x: number, y: number): ImmutablePoint {
        return new ImmutablePoint(x, y);
    }
}

class MutablePoint extends Point<MutablePoint> {
    public x: number;
    public y: number;

    protected update(x: number, y: number): MutablePoint {
        ...
        return this;
    }
}


const pointA: ImmutablePoint = new ImmutablePoint(10, 10)
const pointB: ImmutablePoint = new ImmutablePoint(6, 2);
const result: ImmutablePoint = pointA.diff(pointB); // fine

(code in playground)

With the new Default generic type variables feature available you should be able to do something like:

abstract class Point<T extends Point = Point> {
  ...
}

(haven't tested it yet)

like image 37
Nitzan Tomer Avatar answered Sep 22 '22 00:09

Nitzan Tomer