Polymorf this i TypeScript


Jag försöker göra det jag tror är ett "läroboksexempel" på användning av polymorf this och det fungerar inte och jag kan helt enkelt inte lista ut det. Föreställ dig att jag har någon abstract basklass som kan klonas, t.ex.:

abstract class X {
    abstract clone(): this;
}

Nu vill jag implementera en basklass som tillhandahåller en clone-implementation:

class Y extends X {
    constructor(private x: number) { super() }
    clone(): this { return new Y(this.x + 1); }
}

När jag gör detta får jag ett fel som säger Type Y is not assignable to type 'this'. Jag är helt förvirrad. Allt jag vill förmedla här är typbegränsningen att om en subklass av X har sin clone-metod anropad, så kommer typen av det objekt du får tillbaka vara identisk med subtypen. Är inte detta exakt vad polymorf this är till för? Vad gör jag fel här?

Här är en länk till den här koden i TypeScript-lekplatsen.

Svar #1

Jag har markerat Artems svar som det rätta eftersom han verkar ha rätt i att det verkligen inte är möjligt att göra detta på ett 100% säkert sätt.

Det finns dock ett sätt att få kompilatorn att upprätthålla den typbegränsning jag ville ha. Så jag bestämde mig för att inkludera detta svar ifall det är användbart för folk. Den enda nackdelen med mitt tillvägagångssätt är att det är något osäkert av exakt de skäl som artem påpekar, dvs. att någon skulle kunna utöka dina klasser utan att tillhandahålla en implementation av clone och skapa en situation där det returnerade värdet egentligen inte är vad du påstår att det är.

Min lösning var att helt enkelt lägga till en typomvandling. Återigen, detta är allmänt osäkert. Men om du aldrig utökar från klasserna fungerar det bra såvitt jag kan se. Så min lösning var:

class Y extends X {
    constructor(private x: number) { super() }
    clone(): this {
        return new Y(this.x + 1) as this;
    }
}

Du kan se en komplett version som kompilerar här.

Svar #2

Nej, som saker och ting är just nu, är det inte ett stödt användningsfall för denna typ. En metod med this-returtyp bör returnera en instans av den avledda klassen även om denna metod inte är överskriven i den avledda klassen - se koden i detta svar för ett exempel som motiverar detta.

Med andra ord, givet abstract clone(): this deklaration, måste denna kod vara giltig även om Z inte överskrider clone själv, men din implementation av clone i Y bryter den.

class Z extends Y {
    f() {
        let c: Z = this.clone();
    }
}

Så det verkar som att en metod med this som returtyp alltid bör return this.

UPPDATERING: Jag hittade ett öppet problem med detta användningsfall på GitHub, markerat som 'accepterar pull requests'. Jag är dock inte säker på om de verkligen tänker stödja det, eller planerar att fixa cloneNode i deras kompilator på något annat sätt.

Relaterade artiklar