C'è un modo per creare interfacce in ES6 / Node 4?


110

ES6 è completamente disponibile nel nodo 4. Mi chiedevo se includesse un concetto di interfaccia per definire i contratti di metodo come in MyClass implements MyInterface.

Non riesco a trovare molto con il mio Google, ma forse è disponibile un bel trucco o una soluzione alternativa.


2
Completamente? Di gran lunga no.
Bergi

1
JS usa ancora la dattilografia . Non esistono "contratti di metodo" applicati staticamente. Se vuoi testarli dinamicamente, puoi facilmente scrivere il tuo controllo dell'interfaccia.
Bergi

26
In ritardo alla festa, ma non sono d'accordo, la questione è fuori tema. OP vuole la conferma se esiste una caratteristica prevista. La nuova sintassi semplificata per le classi è attesa da tempo e sarà probabilmente ampiamente utilizzata. Ma le interfacce sono comuni in altre lingue per ottime ragioni. Anch'io sono rimasto sorpreso e deluso nell'apprendere che le interfacce non fanno parte di ES2015. Dato che questa è probabilmente una scoperta comune, IMHO non è irragionevole chiedere se esiste una soluzione alternativa suggerita.

9
Come diavolo è fuori tema? Le interfacce sono una tecnica di programmazione non un prodotto. La domanda è valida ed è buona con il rilascio di ECMA Script 6 che introduce definizioni di classi simili a Java. Penso che la chiusura di questo argomento dimostri la mancanza di comprensione e come su Stack overflow il sistema di punti non sia correlato all'abilità.
Andrew S

4
In nessun punto l'OP (ci chiede) di raccomandare o trovare un libro, uno strumento, una libreria software, un tutorial o un'altra risorsa fuori sede in nessuna di queste domande.
Liam

Risposte:


90

Le interfacce non fanno parte dell'ES6 ma lo sono le classi.

Se ne hai davvero bisogno, dovresti guardare TypeScript che li supporta .


1
"loro" essendo interfacce. FWIW Potrebbe essere necessario considerare attentamente il collegamento per il trasportatore fornito sopra. Non esattamente come mi aspettavo, ma vicino.

Una nota: per quanto ne so, l'interfaccia pura in TypeScript non si trasforma in nulla. Solo se li usi, il codice traspilato ha una certa logica.
Daniel Danielecki

9

Nei commenti debiasej ha scritto l'articolo di seguito menzionato spiega di più sui modelli di progettazione (basati su interfacce, classi):

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

Anche il libro Design patterns in javascript potrebbe esserti utile:

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

Modello di progettazione = classi + interfaccia o ereditarietà multipla

Un esempio del pattern factory in ES6 JS (da eseguire: 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
Allora, dov'è qui un'interfaccia che posso comporre con gli altri?
Dmitri Zaitsev

Alcune spiegazioni più approfondite sono su questo sito: sitepoint.com/object-oriented-javascript-deep-dive-es6-classes
42n4

2
C'è un ottimo aggiornamento dei pattern da ES5 a ES6 su questo sito: loredanacirstea.github.io/es6-design-patterns
debiasej

8

Dato che l'ECMA è un linguaggio "senza classi", l'implementazione della composizione classica non ha - ai miei occhi - molto senso. Il pericolo è che, così facendo, si stia effettivamente tentando di riprogettare il linguaggio (e, se ci si sente fortemente al riguardo, ci sono eccellenti soluzioni olistiche come il già citato TypeScript che mitigano la reinvenzione della ruota)

Questo non vuol dire che la composizione sia comunque fuori discussione in Plain Old JS. Ho studiato a lungo questo aspetto qualche tempo fa. Il candidato più forte che ho visto per la gestione della composizione all'interno del paradigma del prototipo di oggetto è stampit , che ora utilizzo in un'ampia gamma di progetti. E, soprattutto, aderisce a una specifica ben articolata.

maggiori informazioni sui francobolli qui


1
Rimango al mio posto anche con un -1. Purtroppo, a volte questa è la democrazia di SO. Spero che qualcuno trovi utili i link. Stampit vale il tuo tempo.
Jay Edwards

-1 non è un verdetto finale. Il tuo post potrebbe finire con + 100 / -1. Tuttavia penso ancora che sia vago. JS non è più "privo di classi". Sospetto che anche "composizione classica" non sarà inteso dalla maggior parte delle persone nel senso di ciò che intendevi: eredità. (Considera l'intera eredità contro la composizione della guerra santa.) Inoltre non è chiaro cosa sia "Plain Old JS". ES5? Sebbene con una sintassi più prolissa, supportava tecniche che sono più diffuse ora, come i "veri" mix-in . I francobolli sembrano interessanti, quali sono i loro vantaggi rispetto ai mix-in?
ᆼ ᆺ ᆼ

la parola chiave della classe è zucchero sintattico. JS - ES ^ 6 o altro - non è un linguaggio di classe. si limita a decorare il tradizionale approccio del costruttore di funzioni in ES5. Pertanto, "plain old JS" definisce felicemente qualsiasi implementazione JS di ES. Francamente, vorrei che la decisione non fosse stata presa per rafforzare ulteriormente l'idea di classe nel linguaggio quora.com/Are-ES6-classes-bad-for-JavaScript. I francobolli riflettono meglio i punti di forza di JS IMHO. stampit.js.org fornisce una buona panoramica delle differenze tra le classi. In definitiva, è una metodologia più pragmatica.
Jay Edwards

1
Ma allora, cos'è un "linguaggio di classe" ? C ++? classè solo un sinonimo di struct. Un linguaggio davvero classico come Smalltalk? Esso consente l'estensione dinamica di prototipi e casi persino
ᆼ ᆺ ᆼ

Questo è un punto ragionevole. Definirei un linguaggio di classe come un linguaggio che è intrinsecamente OOP. Da MDN: "JavaScript è un linguaggio dinamico, multi-paradigma, basato su prototipi, che supporta stili orientati agli oggetti, imperativi e dichiarativi (ad es. Programmazione funzionale)". google.com/url?sa=t&source=web&rct=j&url=https://…
Jay Edwards

6

Questa è la mia soluzione al problema. È possibile "implementare" più interfacce sostituendo un'interfaccia con un'altra.

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'

MODIFICARE:

Ho migliorato il codice in modo che ora puoi semplicemente usare implement (baseClass, interface1, interface2, ...) nell'estensione.

/**
* 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'

0

ci sono pacchetti che possono simulare interfacce.

puoi usare l' interfaccia es6


2
il problema con la tua risposta è che non ha chiesto uno "strumento" per farlo. ma come si faceva queni sarebbe stata più corretta una risposta che spiegasse come lo faceva quella forma.
Gianfrancesco Aurecchia
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.