Gibt es eine Möglichkeit, Schnittstellen in ES6 / Node 4 zu erstellen?


109

ES6 ist in Knoten 4 vollständig verfügbar. Ich habe mich gefragt, ob es ein Schnittstellenkonzept zum Definieren von Methodenverträgen wie in enthält MyClass implements MyInterface.

Ich kann mit meinem Googeln nicht viel finden, aber vielleicht gibt es einen netten Trick oder eine Problemumgehung.



1
JS verwendet immer noch die Ententypisierung . Es gibt keine statisch erzwungenen "Methodenverträge". Wenn Sie sie dynamisch testen möchten, können Sie ganz einfach Ihren eigenen Schnittstellenprüfer schreiben.
Bergi

26
Spät zur Party, aber nicht einverstanden ist die Frage nicht zum Thema. OP möchte eine Bestätigung, wenn eine erwartete Funktion vorhanden ist. Die neue, vereinfachte Syntax für Klassen ist längst überfällig und wird wahrscheinlich weit verbreitet sein. Schnittstellen sind jedoch aus gutem Grund in anderen Sprachen üblich. Auch ich war überrascht und enttäuscht zu erfahren, dass Schnittstellen nicht Teil von ES2015 sind. Angesichts der Tatsache, dass dies wahrscheinlich eine häufige Entdeckung ist, ist es meiner Meinung nach nicht unangemessen zu fragen, ob es eine vorgeschlagene Problemumgehung gibt.

9
Wie um alles in der Welt ist das nicht zum Thema? Schnittstellen sind Programmiertechniken kein Produkt. Die Frage ist gültig und gut, da ECMA Script 6 Java-ähnliche Klassendefinitionen enthält. Ich denke, das Schließen dieses Themas zeigt das Unverständnis und wie das Punktesystem beim Stapelüberlauf nicht mit der Fähigkeit korreliert.
Andrew S

4
Das OP (bittet) uns buchstäblich zu keinem Zeitpunkt , ein Buch, ein Tool, eine Softwarebibliothek, ein Tutorial oder eine andere externe Ressource in einer dieser Fragen zu empfehlen oder zu finden .
Liam

Antworten:


89

Schnittstellen sind nicht Teil des ES6, Klassen jedoch.

Wenn Sie sie wirklich brauchen, sollten Sie sich TypeScript ansehen, das sie unterstützt .


1
"sie" sind Schnittstellen. FWIW Möglicherweise müssen Sie den oben angegebenen Link für den Transpiler sorgfältig prüfen. Nicht genau wie ich erwartet hatte, aber nah.

Ein Hinweis: Soweit mir bekannt ist, wird die reine Schnittstelle in TypeScript zu nichts transpiliert. Nur wenn Sie sie verwenden, hat der transpilierte Code eine bestimmte Logik.
Daniel Danielecki

9

In Kommentaren, die debiasej geschrieben hat, erklärt der unten erwähnte Artikel mehr über Entwurfsmuster (basierend auf Schnittstellen, Klassen):

http://loredanacirstea.github.io/es6-design-patterns/

Design Patterns Book in Javascript kann auch für Sie nützlich sein:

http://addyosmani.com/resources/essentialjsdesignpatterns/book/

Entwurfsmuster = Klassen + Schnittstelle oder Mehrfachvererbung

Ein Beispiel für das Factory-Muster in ES6 JS (zum Ausführen: node example.js):

"use strict";

// Types.js - Constructors used behind the scenes

// A constructor for defining new cars
class Car {
  constructor(options){
    console.log("Creating Car...\n");
    // some defaults
    this.doors = options.doors || 4;
    this.state = options.state || "brand new";
    this.color = options.color || "silver";
  }
}

// A constructor for defining new trucks
class Truck {
  constructor(options){
    console.log("Creating Truck...\n");
    this.state = options.state || "used";
    this.wheelSize = options.wheelSize || "large";
    this.color = options.color || "blue";
  }
}


// FactoryExample.js

// Define a skeleton vehicle factory
class VehicleFactory {}

// Define the prototypes and utilities for this factory

// Our default vehicleClass is Car
VehicleFactory.prototype.vehicleClass = Car;

// Our Factory method for creating new Vehicle instances
VehicleFactory.prototype.createVehicle = function ( options ) {

  switch(options.vehicleType){
    case "car":
      this.vehicleClass = Car;
      break;
    case "truck":
      this.vehicleClass = Truck;
      break;
    //defaults to VehicleFactory.prototype.vehicleClass (Car)
  }

  return new this.vehicleClass( options );

};

// Create an instance of our factory that makes cars
var carFactory = new VehicleFactory();
var car = carFactory.createVehicle( {
            vehicleType: "car",
            color: "yellow",
            doors: 6 } );

// Test to confirm our car was created using the vehicleClass/prototype Car

// Outputs: true
console.log( car instanceof Car );

// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
console.log( car );

var movingTruck = carFactory.createVehicle( {
                      vehicleType: "truck",
                      state: "like new",
                      color: "red",
                      wheelSize: "small" } );

// Test to confirm our truck was created with the vehicleClass/prototype Truck

// Outputs: true
console.log( movingTruck instanceof Truck );

// Outputs: Truck object of color "red", a "like new" state
// and a "small" wheelSize
console.log( movingTruck );

34
Wo ist hier eine Schnittstelle, die ich mit anderen zusammenstellen kann?
Dmitri Zaitsev

Einige tiefere Erklärungen finden Sie auf dieser Site: sitepoint.com/object-oriented-javascript-deep-dive-es6-classes
42n4

2
Es gibt ein großartiges Update von ES5-Mustern auf ES6 auf dieser Site: loredanacirstea.github.io/es6-design-patterns
debiasej

8

Angesichts der Tatsache, dass ECMA eine „klassenfreie“ Sprache ist, ist die Implementierung klassischer Kompositionen in meinen Augen nicht sehr sinnvoll. Die Gefahr besteht darin, dass Sie auf diese Weise effektiv versuchen, die Sprache neu zu konstruieren (und wenn Sie sich stark dafür fühlen, gibt es ausgezeichnete ganzheitliche Lösungen wie das oben erwähnte TypeScript, die die Neuerfindung des Rads mildern).

Das heißt aber nicht, dass Komposition in Plain Old JS jedoch nicht in Frage kommt. Ich habe das vor einiger Zeit ausführlich recherchiert. Der stärkste Kandidat, den ich für den Umgang mit Kompositionen innerhalb des Objektprototyp-Paradigmas gesehen habe, ist Stempel , den ich jetzt in einer Vielzahl von Projekten verwende. Und vor allem hält es sich an eine gut artikulierte Spezifikation.

Weitere Informationen zu Briefmarken finden Sie hier


1
Ich stehe zu meinem Beitrag auch mit -1. Leider ist das manchmal die Demokratie von SO. Ich hoffe, jemand findet die Links nützlich. Stampit ist Ihre Zeit wert.
Jay Edwards

-1 ist kein endgültiges Urteil. Ihr Beitrag könnte am Ende + 100 / -1 enden. Ich denke jedoch immer noch, dass es vage ist. JS ist nicht mehr "klassenfrei". Ich vermute, dass "klassische Komposition" von den meisten auch nicht so verstanden wird, wie Sie es gemeint haben: Vererbung. (Betrachten Sie das gesamte Erbe gegen den heiligen Krieg der Komposition.) Es ist auch nicht klar, was "Plain Old JS" ist. ES5? Obwohl mit einer ausführlicheren Syntax, wurden Techniken unterstützt, die heute weiter verbreitet sind, wie beispielsweise "echte" Mix-Ins . Briefmarken sehen interessant aus. Was sind ihre Vorteile gegenüber Mix-Ins?
ᆼ ᆺ ᆼ

Das Schlüsselwort class lautet syntaktischer Zucker. JS - ES ^ 6 oder anders - ist keine Klassensprache. Es dekoriert lediglich den traditionellen Funktionskonstruktor-Ansatz in ES5. "plain old JS" definiert daher gerne eine der JS-Implementierungen von ES. Ehrlich gesagt, ich wünschte, die Entscheidung wäre nicht getroffen worden, um die Idee des Unterrichts in der Sprache quora.com/Are-ES6-classes-bad-for-JavaScript weiter zu verankern. Briefmarken spiegeln meiner Meinung nach die Stärken von JS besser wider. stampit.js.org gibt einen guten Überblick über die Unterschiede zu Klassen. Letztendlich ist es eine pragmatischere Methode.
Jay Edwards

1
Aber was ist dann eine "Klassensprache" ? C ++? classist nur ein Synonym für struct. Eine wirklich klassische Sprache wie Smalltalk? Es ermöglicht die dynamische Erweiterung von Prototypen und sogar Instanzen
ᆼ ᆺ ᆼ

Das ist ein vernünftiger Punkt. Ich würde eine Klassensprache als eine Sprache definieren, die an sich OOP ist. Von MDN: "JavaScript ist eine prototypbasierte, paradigmenbasierte, dynamische Sprache, die objektorientierte, imperative und deklarative (z. B. funktionale Programmierung) Stile unterstützt." google.com/url?sa=t&source=web&rct=j&url=https://…
Jay Edwards

6

Dies ist meine Lösung für das Problem. Sie können mehrere Schnittstellen 'implementieren', indem Sie eine Schnittstelle mit einer anderen überschreiben.

class MyInterface {
    // Declare your JS doc in the Interface to make it acceable while writing the Class and for later inheritance
    /**
     * Gives the sum of the given Numbers
     * @param {Number} a The first Number
     * @param {Number} b The second Number
     * @return {Number} The sum of the Numbers
     */
    sum(a, b) { this._WARNING('sum(a, b)'); }


    // delcare a warning generator to notice if a method of the interface is not overridden
    // Needs the function name of the Interface method or any String that gives you a hint ;)
    _WARNING(fName='unknown method') {
        console.warn('WARNING! Function "'+fName+'" is not overridden in '+this.constructor.name);
    }
}

class MultipleInterfaces extends MyInterface {
    // this is used for "implement" multiple Interfaces at once
    /**
     * Gives the square of the given Number
     * @param {Number} a The Number
     * @return {Number} The square of the Numbers
     */
    square(a) { this._WARNING('square(a)'); }
}

class MyCorrectUsedClass extends MyInterface {
    // You can easy use the JS doc declared in the interface
    /** @inheritdoc */
    sum(a, b) {
        return a+b;
    }
}
class MyIncorrectUsedClass extends MyInterface {
    // not overriding the method sum(a, b)
}

class MyMultipleInterfacesClass extends MultipleInterfaces {
    // nothing overriden to show, that it still works
}


let working = new MyCorrectUsedClass();

let notWorking = new MyIncorrectUsedClass();

let multipleInterfacesInstance = new MyMultipleInterfacesClass();

// TEST IT

console.log('working.sum(1, 2) =', working.sum(1, 2));
// output: 'working.sum(1, 2) = 3'

console.log('notWorking.sum(1, 2) =', notWorking.sum(1, 2));
// output: 'notWorking.sum(1, 2) = undefined'
// but also sends a warn to the console with 'WARNING! Function "sum(a, b)" is not overridden in MyIncorrectUsedClass'

console.log('multipleInterfacesInstance.sum(1, 2) =', multipleInterfacesInstance.sum(1, 2));
// output: 'multipleInterfacesInstance.sum(1, 2) = undefined'
// console warn: 'WARNING! Function "sum(a, b)" is not overridden in MyMultipleInterfacesClass'

console.log('multipleInterfacesInstance.square(2) =', multipleInterfacesInstance.square(2));
// output: 'multipleInterfacesInstance.square(2) = undefined'
// console warn: 'WARNING! Function "square(a)" is not overridden in MyMultipleInterfacesClass'

BEARBEITEN:

Ich habe den Code verbessert, sodass Sie jetzt einfach implement (baseClass, interface1, interface2, ...) im Extend verwenden können.

/**
* Implements any number of interfaces to a given class.
* @param cls The class you want to use
* @param interfaces Any amount of interfaces separated by comma
* @return The class cls exteded with all methods of all implemented interfaces
*/
function implement(cls, ...interfaces) {
    let clsPrototype = Object.getPrototypeOf(cls).prototype;
    for (let i = 0; i < interfaces.length; i++) {
        let proto = interfaces[i].prototype;
        for (let methodName of Object.getOwnPropertyNames(proto)) {
            if (methodName!== 'constructor')
                if (typeof proto[methodName] === 'function')
                    if (!clsPrototype[methodName]) {
                        console.warn('WARNING! "'+methodName+'" of Interface "'+interfaces[i].name+'" is not declared in class "'+cls.name+'"');
                        clsPrototype[methodName] = proto[methodName];
                    }
        }
    }
    return cls;
}

// Basic Interface to warn, whenever an not overridden method is used
class MyBaseInterface {
    // declare a warning generator to notice if a method of the interface is not overridden
    // Needs the function name of the Interface method or any String that gives you a hint ;)
    _WARNING(fName='unknown method') {
        console.warn('WARNING! Function "'+fName+'" is not overridden in '+this.constructor.name);
    }
}


// create a custom class
/* This is the simplest example but you could also use
*
*   class MyCustomClass1 extends implement(MyBaseInterface) {
*       foo() {return 66;}
*   }
*
*/
class MyCustomClass1 extends MyBaseInterface {
    foo() {return 66;}
}

// create a custom interface
class MyCustomInterface1 {
     // Declare your JS doc in the Interface to make it acceable while writing the Class and for later inheritance

    /**
     * Gives the sum of the given Numbers
     * @param {Number} a The first Number
     * @param {Number} b The second Number
     * @return {Number} The sum of the Numbers
     */
    sum(a, b) { this._WARNING('sum(a, b)'); }
}

// and another custom interface
class MyCustomInterface2 {
    /**
     * Gives the square of the given Number
     * @param {Number} a The Number
     * @return {Number} The square of the Numbers
     */
    square(a) { this._WARNING('square(a)'); }
}

// Extend your custom class even more and implement the custom interfaces
class AllInterfacesImplemented extends implement(MyCustomClass1, MyCustomInterface1, MyCustomInterface2) {
    /**
    * @inheritdoc
    */
    sum(a, b) { return a+b; }

    /**
    * Multiplies two Numbers
    * @param {Number} a The first Number
    * @param {Number} b The second Number
    * @return {Number}
    */
    multiply(a, b) {return a*b;}
}


// TEST IT

let x = new AllInterfacesImplemented();

console.log("x.foo() =", x.foo());
//output: 'x.foo() = 66'

console.log("x.square(2) =", x.square(2));
// output: 'x.square(2) = undefined
// console warn: 'WARNING! Function "square(a)" is not overridden in AllInterfacesImplemented'

console.log("x.sum(1, 2) =", x.sum(1, 2));
// output: 'x.sum(1, 2) = 3'

console.log("x.multiply(4, 5) =", x.multiply(4, 5));
// output: 'x.multiply(4, 5) = 20'

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.