Existe-t-il un moyen de créer des interfaces dans ES6 / Node 4?

110

ES6 est entièrement disponible dans Node 4. Je me demandais s'il inclut un concept d'interface pour définir les contrats de méthode comme dans MyClass implements MyInterface.

Je ne trouve pas grand-chose avec ma recherche sur Google, mais il existe peut-être une belle astuce ou une solution de contournement disponible.

Jérôme Verstrynge
la source
2
Pleinement? De loin pas.
Bergi
1
JS utilise toujours le typage canard . Il n'y a pas de "contrats de méthode" imposés statiquement. Si vous souhaitez les tester dynamiquement, vous pouvez facilement écrire votre propre vérificateur d'interface.
Bergi
26
En retard à la fête, mais en désaccord, la question est hors sujet. OP veut une confirmation si une fonctionnalité attendue existe. La nouvelle syntaxe simplifiée pour les classes est attendue depuis longtemps et sera probablement largement utilisée. Mais les interfaces sont courantes dans d'autres langues pour une très bonne raison. J'ai aussi été surpris et déçu d'apprendre que les interfaces ne font pas partie de l'ES2015. Étant donné qu'il s'agit probablement d'une découverte commune, à mon humble avis, il n'est pas déraisonnable de demander s'il existe une solution de contournement suggérée.
9
Comment diable est-ce hors sujet? Les interfaces sont une technique de programmation et non un produit. La question est valide et est bonne avec la sortie d'ECMA Script 6 apportant Java comme des définitions de classe. Je pense que la clôture de ce sujet démontre le manque de compréhension et comment, lors du débordement de la pile, le système de points ne correspond pas à la capacité.
Andrew S
4
À aucun moment, l'OP ne nous (demande) de recommander ou de trouver un livre, un outil, une bibliothèque de logiciels, un didacticiel ou une autre ressource hors site dans l'une de ces questions.
Liam

Réponses:

90

Les interfaces ne font pas partie de l'ES6 mais les classes le sont.

Si vous en avez vraiment besoin, vous devriez regarder TypeScript qui les prend en charge .

Gaelgillard
la source
1
«eux» étant des interfaces. FWIW Vous devrez peut-être examiner attentivement le lien du transpilateur ci-dessus. Pas exactement comme je m'y attendais, mais proche.
Une note: pour autant que je sache, l'interface pure dans TypeScript transpile à rien. Seulement si vous les utilisez, le code transpilé a une certaine logique.
Daniel Danielecki le
9

Dans les commentaires, debiasej a écrit l'article mentionné ci-dessous pour en savoir plus sur les modèles de conception (basés sur les interfaces, les classes):

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

Un livre de modèles de conception en javascript peut également vous être utile:

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

Design pattern = classes + interface ou héritage multiple

Un exemple du modèle d'usine dans ES6 JS (pour exécuter: 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 );
42n4
la source
34
Alors, où est ici une interface que je peux composer avec les autres?
Dmitri Zaitsev
Des explications plus approfondies sont disponibles sur ce site: sitepoint.com/object-oriented-javascript-deep-dive-es6-classes
42n4
2
Il y a une excellente mise à jour des modèles ES5 vers ES6 sur ce site: loredanacirstea.github.io/es6-design-patterns
debiasej
8

Étant donné que l'ECMA est un langage «sans classe», la mise en œuvre de la composition classique n'a pas - à mes yeux - beaucoup de sens. Le danger est que, ce faisant, vous tentez effectivement de réorganiser le langage (et, si vous en êtes convaincu, il existe d'excellentes solutions holistiques telles que le TypeScript susmentionné qui atténuent la réinvention de la roue)

Cela ne veut pas dire que la composition est cependant hors de question dans Plain Old JS. J'ai longuement recherché cela il y a quelque temps. Le candidat le plus fort que j'ai vu pour gérer la composition dans le paradigme du prototypage d'objet est stampit , que j'utilise maintenant dans un large éventail de projets. Et, surtout, il adhère à une spécification bien articulée.

plus d'informations sur les timbres ici

Jay Edwards
la source
1
Je reste fidèle à mon poste même avec un -1. Malheureusement, c'est parfois la démocratie de SO. J'espère que quelqu'un trouve les liens utiles. Stampit vaut votre temps.
Jay Edwards
-1 n'est pas un verdict final. Votre message pourrait finir par + 100 / -1. Cependant, je pense toujours que c'est vague. JS n'est plus "sans classe". Je soupçonne que «composition classique» ne sera pas non plus compris par la plupart des gens comme signifiant ce que vous vouliez dire: l'héritage. (Considérez l'héritage entier par rapport à la guerre sainte de composition.) Il n'est pas non plus clair ce qu'est "Plain Old JS". ES5? Bien qu'avec une syntaxe plus verbeuse, il supportait des techniques plus répandues maintenant, telles que les "vrais" mix-ins . Les timbres semblent intéressants, quels sont leurs avantages par rapport aux mix-ins?
ᆼ ᆺ ᆼ
le mot-clé de classe est le sucre syntaxique. JS - ES ^ 6 ou autre - n'est pas une langue de classe. il décore simplement l'approche traditionnelle du constructeur de fonctions dans ES5. "plain old JS" définit donc avec bonheur toutes les implémentations JS d'ES. Franchement, j'aurais aimé que la décision n'ait pas été prise pour enraciner davantage l'idée de classe dans le langage quora.com/Are-ES6-classes-bad-for-JavaScript. Les timbres reflètent mieux les forces de JS IMHO. stampit.js.org donne un bon aperçu des différences entre les classes. En fin de compte, c'est une méthodologie plus pragmatique.
Jay Edwards
1
Mais alors, qu'est-ce qu'un «langage de classe» ? C ++? classest juste un synonyme de struct. Un langage vraiment classique comme Smalltalk? Il permet une extension dynamique des prototypes et même des instances
ᆼ ᆺ ᆼ
C'est un point raisonnable. Je définirais un langage de classe comme un langage qui est intrinsèquement POO. De MDN: "JavaScript est un langage dynamique basé sur un prototype, multi-paradigme, prenant en charge les styles orientés objet, impératifs et déclaratifs (par exemple la programmation fonctionnelle)." google.com/url?sa=t&source=web&rct=j&url=https://…
Jay Edwards
6

Ceci est ma solution au problème. Vous pouvez «implémenter» plusieurs interfaces en remplaçant une interface par une autre.

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'

ÉDITER:

J'ai amélioré le code afin que vous puissiez maintenant simplement utiliser implément (baseClass, interface1, interface2, ...) dans l'extension.

/**
* 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'
Kai Lehmann
la source
0

il existe des packages qui peuvent simuler des interfaces.

vous pouvez utiliser l' interface es6

Amit Wagner
la source
2
le problème avec votre réponse est qu'il n'a pas demandé un «outil» pour le faire. mais comment cela a été fait queni aurait été une réponse plus correcte qui expliquerait comment cette forme le faisait.
Gianfrancesco Aurecchia