Website-Suche

Konstruktorüberladung in TypeScript


Hat jemand Konstruktorüberladung in TypeScript gemacht? Auf Seite 64 der Sprachspezifikation (v 0.8) gibt es Aussagen, die Konstruktorüberladungen beschreiben, aber es wurde kein Beispielcode gegeben.

Ich probiere gerade eine wirklich einfache Klassendeklaration aus; sie sieht so aus,

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj: IBox) {    
        this.x = obj.x;
        this.y = obj.y;
        this.height = obj.height;
        this.width = obj.width;
    }   

    constructor() {
        this.x = 0;
        this.y = 0;
        this.width = 0;
        this.height = 0;
    }
}

Wenn man BoxSample.ts mit tsc ausführt, wird eine doppelte Konstruktordefinition angezeigt - was offensichtlich ist. Jede Hilfe wird geschätzt.

Antwort #1

TypeScript erlaubt es Ihnen, Überladungen zu deklarieren, aber Sie können nur eine Implementierung haben, und diese Implementierung muss eine Signatur haben, die mit allen Überladungen kompatibel ist. In Ihrem Beispiel kann dies leicht mit einem optionalen Parameter wie folgt erfolgen,

interface IBox {    
    x: number;
    y: number;
    height: number;
    width: number;
}
    
class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj?: IBox) {    
        this.x = obj?.x ?? 0;
        this.y = obj?.y ?? 0;
        this.height = obj?.height ?? 0;
        this.width = obj?.width ?? 0;
    }   
}

oder zwei Überladungen mit einem allgemeineren Konstruktor wie in,

interface IBox {    
    x: number;
    y: number;
    height: number;
    width: number;
}
    
class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor();
    constructor(obj: IBox); 
    constructor(obj?: IBox) {    
        this.x = obj?.x ?? 0;
        this.y = obj?.y ?? 0;
        this.height = obj?.height ?? 0;
        this.width = obj?.width ?? 0;
    }   
}

Im Spielplatz ansehen

Antwort #2

Bezüglich Konstruktorüberladungen wäre eine gute Alternative, die zusätzlichen Überladungen als statische Fabrikmethoden zu implementieren. Ich denke, es ist lesbarer und einfacher, als alle möglichen Argumentkombinationen im Konstruktor zu überprüfen.

Im folgenden Beispiel können wir ein Patientenobjekt unter Verwendung von Daten eines Versicherungsanbieters erstellen, der Werte anders speichert. Um eine weitere Datenstruktur für die Instanziierung von Patienten zu unterstützen, könnte man einfach eine weitere statische Methode hinzufügen, um den Standardkonstruktor so gut wie möglich nach der Normalisierung der bereitgestellten Daten aufzurufen.

class Patient {
    static fromInsurance({
        first, middle = '', last,
        birthday, gender
    }: InsuranceCustomer): Patient {
        return new this(
            `${last}, ${first} ${middle}`.trim(),
            utils.age(birthday),
            gender
        );
    }

    constructor(
        public name: string,
        public age: number,
        public gender?: string
    ) {}
}

interface InsuranceCustomer {
    first: string,
    middle?: string,
    last: string,
    birthday: string,
    gender: 'M' | 'F'
}


const utils = { /* included in the playground link below */};

{// Two ways of creating a Patient instance
    const
        jane = new Patient('Doe, Jane', 21),
        alsoJane = Patient.fromInsurance({ 
            first: 'Jane', last: 'Doe',
            birthday: 'Jan 1, 2000', gender: 'F'
        })

    console.clear()
    console.log(jane)
    console.log(alsoJane)
}

Sie können die Ausgabe im TS Playground überprüfen


Methodenüberladung in TypeScript ist nicht wirklich existent, sozusagen, da sie zu viel vom Compiler generierten Code erfordern würde und TS darauf ausgelegt ist, dies um jeden Preis zu vermeiden. Der Hauptanwendungsfall für Methodenüberladung besteht wahrscheinlich darin, Deklarationen für Bibliotheken zu schreiben, die magische Argumente in ihrer API haben. Da die gesamte harte Arbeit, verschiedene mögliche Argumentmengen zu handhaben, von Ihnen übernommen wird, sehe ich keinen großen Vorteil darin, Überladungen anstelle von Ad-hoc-Methoden für jedes Szenario zu verwenden.

Antwort #3

Es klingt so, als ob du möchtest, dass der Objektparameter optional ist und dass auch jede der Eigenschaften im Objekt optional ist. Im bereitgestellten Beispiel wird die Überladungssyntax nicht benötigt. Ich wollte auf einige schlechte Praktiken in einigen der Antworten hier hinweisen. Zugegeben, es ist nicht der kleinste mögliche Ausdruck von im Wesentlichen box={ x: 0, y: 87, width: 4, height: 0 } zu schreiben, aber dies bietet alle Code-Hinweis-Vorteile, die du von der beschriebenen Klasse erwarten könntest. Dieses Beispiel ermöglicht es dir, eine Funktion mit einem, einigen, allen oder keinem der Parameter aufzurufen und trotzdem Standardwerte zu erhalten.

 /** @class */
 class Box {
     public x?: number;
     public y?: number;
     public height?: number;
     public width?: number;   

     // The Box class can work double-duty as the interface here since 
     // they are identical.  If you choose to add methods or modify this class, 
     // you will need to define and reference a new interface 
     // for the incoming parameters object 
     // e.g.:  `constructor(params: BoxObjI = {} as BoxObjI)` 
     constructor(params: Box = {} as Box) {

         // Define the properties of the incoming `params` object here. 
         // Setting a default value with the `= 0` syntax is optional for each parameter
         const {
             x = 0,
             y = 0,
             height = 1,
             width = 1
         } = params;
         
         //  If needed, make the parameters publicly accessible
         //  on the class ex.: 'this.var = var'.
         /**  Use jsdoc comments here for inline ide auto-documentation */
         this.x = x;
         this.y = y;
         this.height = height;
         this.width = width;
     }
 }

Müssen Methoden hinzugefügt werden? Eine ausführliche, aber erweiterbare Alternative: Die obige Box-Klasse kann doppelt verwendet werden, da sie als Schnittstelle dienen kann, weil sie identisch sind. Wenn Sie sich entscheiden, die obige Klasse zu ändern, müssen Sie eine neue Schnittstelle für das eingehende Parameterobjekt definieren und darauf verweisen, da die Box-Klasse nicht mehr genau wie die eingehenden Parameter aussehen würde. Beachten Sie, wo in diesem Fall die Fragezeichen (?:), die optionale Eigenschaften kennzeichnen, erscheinen. Da wir innerhalb der Klasse Standardwerte festlegen, sind diese garantiert vorhanden, sie sind jedoch innerhalb des eingehenden Parameterobjekts optional:

    interface BoxParams {
        x?: number;
         // Add Parameters ...
    }

    class Box {
         public x: number;
         // Copy Parameters ...
         constructor(params: BoxParams = {} as BoxParams) {
         let { x = 0 } = params;
         this.x = x;
    }
    doSomething = () => {
        return this.x + this.x;
        }
    }

Egal, wie Sie Ihre Klasse definieren, bietet diese Technik die Schutzmechanismen der Typensicherheit, jedoch die Flexibilität, beliebige dieser zu schreiben:

const box1 = new Box();
const box2 = new Box({});
const box3 = new Box({x: 0});
const box4 = new Box({x: 0, height: 10});
const box5 = new Box({x: 0, y: 87, width: 4, height: 0});

 // Correctly reports error in TypeScript, and in js, box6.z is undefined
const box6 = new Box({z: 0});  

Kompiliert sehen Sie, wie die Standardeinstellungen nur verwendet werden, wenn ein optionaler Wert undefiniert ist; es vermeidet die Fallstricke einer weit verbreiteten (aber fehleranfälligen) Fallback-Syntax von var=isOptional || default;, indem gegen void 0 geprüft wird, was eine Kurzform für undefined ist:

Die kompilierte Ausgabe

var Box = (function () {
    function Box(params) {
        if (params === void 0) { params = {}; }
        var _a = params.x, x = _a === void 0 ? 0 : _a, _b = params.y, y = _b === void 0 ? 0 : _b, _c = params.height, height = _c === void 0 ? 1 : _c, _d = params.width, width = _d === void 0 ? 1 : _d;
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
    return Box;
}());

Addendum: Standardwerte festlegen: der falsche Weg

Der || (oder) Operator

Betrachten Sie die Gefahr von ||/oder-Operatoren beim Festlegen von Standard-Rückfallwerten, wie in einigen anderen Antworten gezeigt. Der folgende Code veranschaulicht die falsche Art, Standardwerte festzulegen. Sie können unerwartete Ergebnisse erhalten, wenn Sie gegen falsey-Werte wie 0, '', null, undefined, false, NaN auswerten:

var myDesiredValue = 0;
var result = myDesiredValue || 2;

// This test will correctly report a problem with this setup.
console.assert(myDesiredValue === result && result === 0, 'Result should equal myDesiredValue. ' + myDesiredValue + ' does not equal ' + result);

Object.assign(this, params)

In meinen Tests kann die Verwendung eines desktrukturierten Objekts in ES6/TypeScript 15-90 % schneller sein als Object.assign. Die Verwendung eines desktrukturierten Parameters erlaubt nur Methoden und Eigenschaften, die Sie dem Objekt zugewiesen haben. Zum Beispiel betrachten Sie diese Methode:

class BoxTest {
    public x?: number = 1;

    constructor(params: BoxTest = {} as BoxTest) {
        Object.assign(this, params);
    }
}

Wenn ein anderer Benutzer TypeScript nicht verwendete und versuchte, einen Parameter einzufügen, der nicht dazugehört, zum Beispiel könnten sie versuchen, eine z-Eigenschaft einzufügen

var box = new BoxTest({x: 0, y: 87, width: 4, height: 0, z: 7});

// This test will correctly report an error with this setup. `z` was defined even though `z` is not an allowed property of params.
console.assert(typeof box.z === 'undefined')

Antwort #4

Beachten Sie, dass Sie den Mangel an Überladung auf Implementierungsebene auch durch Standardparameter in TypeScript umgehen können, z. B.:

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj : IBox = {x:0,y:0, height:0, width:0}) {    
        this.x = obj.x;
        this.y = obj.y;
        this.height = obj.height;
        this.width = obj.width;
    }   
}

Bearbeiten: Ab dem 5. Dezember 2016 siehe Bensons Antwort für eine ausführlichere Lösung, die mehr Flexibilität erlaubt.

Antwort #5

Update 2 (28. September 2020): Diese Sprache entwickelt sich ständig weiter, und wenn Sie also Partial (eingeführt in v2.1) verwenden können, dann ist dies jetzt meine bevorzugte Methode, dies zu erreichen.

class Box {
   x: number;
   y: number;
   height: number;
   width: number;

   public constructor(b: Partial<Box> = {}) {
      Object.assign(this, b);
   }
}

// Example use
const a = new Box();
const b = new Box({x: 10, height: 99});
const c = new Box({foo: 10});          // Will fail to compile

Aktualisierung (8. Juni 2017): guyarad und snolflake machen in ihren Kommentaren unter meiner Antwort gültige Punkte. Ich würde den Lesern empfehlen, sich die Antworten von Benson, Joe und snolflake anzusehen, die bessere Antworten als meine haben.*

Ursprüngliche Antwort (27. Januar 2014)

Ein weiteres Beispiel dafür, wie man Konstruktorüberladung erreicht:

class DateHour {

  private date: Date;
  private relativeHour: number;

  constructor(year: number, month: number, day: number, relativeHour: number);
  constructor(date: Date, relativeHour: number);
  constructor(dateOrYear: any, monthOrRelativeHour: number, day?: number, relativeHour?: number) {
    if (typeof dateOrYear === "number") {
      this.date = new Date(dateOrYear, monthOrRelativeHour, day);
      this.relativeHour = relativeHour;
    } else {
      var date = <Date> dateOrYear;
      this.date = new Date(date.getFullYear(), date.getMonth(), date.getDate());
      this.relativeHour = monthOrRelativeHour;
    }
  }
}

Quelle: http://mimosite.com/blog/post/2013/04/08/Overloading-in-TypeScript

Antwort #6

Ich weiß, dass dies eine alte Frage ist, aber neu in 1.4 sind Unionstypen; verwenden Sie diese für alle Funktionsüberladungen (einschließlich Konstruktoren). Beispiel:

class foo {
    private _name: any;
    constructor(name: string | number) {
        this._name = name;
    }
}
var f1 = new foo("bar");
var f2 = new foo(1);

Antwort #7

Eigentlich könnte es für diese Antwort zu spät sein, aber du kannst jetzt Folgendes tun:

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor();
    constructor(obj: IBox);
    constructor(obj?: IBox) {    
        this.x = !obj ? 0 : obj.x;
        this.y = !obj ? 0 : obj.y;
        this.height = !obj ? 0 : obj.height;
        this.width = !obj ? 0 : obj.width;
    }
}

Also, anstatt statischer Methoden kannst du das Obige tun. Ich hoffe, es wird dir helfen!!!

Verwandte Artikel