¿Hay alguna forma de crear interfaces en ES6 / Node 4?

110

ES6 está completamente disponible en el Nodo 4. Me preguntaba si incluye un concepto de interfaz para definir contratos de método como en MyClass implements MyInterface.

No puedo encontrar mucho con mi búsqueda en Google, pero tal vez haya un buen truco o una solución alternativa disponible.

Jérôme Verstrynge
fuente
2
¿Completamente? De lejos no.
Bergi
1
JS todavía usa la escritura de pato . No hay "contratos de métodos" que se cumplan estáticamente. Si desea probarlos dinámicamente, puede escribir fácilmente su propio verificador de interfaz.
Bergi
26
Llega tarde a la fiesta, pero no estoy de acuerdo, la pregunta está fuera de tema. OP quiere confirmación si existe una característica esperada. La nueva y simplificada sintaxis de las clases está pendiente desde hace mucho tiempo y probablemente se utilizará ampliamente. Pero las interfaces son comunes en otros lenguajes por muy buenas razones. Yo también me sorprendió y me decepcionó saber que las interfaces no son parte de ES2015. Dado que este es probablemente un descubrimiento común, en mi humilde opinión no es descabellado preguntar si hay una solución alternativa sugerida.
9
¿Cómo diablos es esto fuera de tema? Las interfaces son una técnica de programación, no un producto. La pregunta es válida y buena con el lanzamiento de ECMA Script 6 que trae definiciones de clase similares a Java. Creo que el cierre de este tema demuestra la falta de comprensión y cómo en Stack overflow el sistema de puntos no se correlaciona con la habilidad.
Andrew S
4
Literalmente, en ningún momento el OP (nos pide) que recomiende o busquemos un libro, herramienta, biblioteca de software, tutorial u otro recurso externo en ninguna de estas preguntas.
Liam

Respuestas:

90

Las interfaces no forman parte del ES6, pero las clases sí lo son.

Si realmente los necesita, debería mirar TypeScript que los admite .

Gaelgillard
fuente
1
"ellos" son interfaces. FWIW Es posible que deba considerar detenidamente el enlace del transpilador proporcionado anteriormente. No exactamente como esperaba, pero cerca.
Una nota: hasta donde yo sé, la interfaz pura en TypeScript se convierte en nada. Solo si los usa, el código transpilado tiene cierta lógica.
Daniel Danielecki
9

En los comentarios, debiasej escribió el artículo mencionado a continuación que explica más sobre los patrones de diseño (basados ​​en interfaces, clases):

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

El libro de patrones de diseño en javascript también puede serle útil:

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

Patrón de diseño = clases + interfaz o herencia múltiple

Un ejemplo del patrón de fábrica en ES6 JS (para ejecutar: 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
fuente
34
Entonces, ¿dónde está aquí una interfaz que pueda componer con otros?
Dmitri Zaitsev
Algunas explicaciones más profundas están en este sitio: sitepoint.com/object-oriented-javascript-deep-dive-es6-classes
42n4
2
Hay una gran actualización de los patrones de ES5 a ES6 en este sitio: loredanacirstea.github.io/es6-design-patterns
debiasej
8

Dado que ECMA es un lenguaje "sin clases", implementar la composición clásica no tiene, en mi opinión, mucho sentido. El peligro es que, al hacerlo, está intentando efectivamente rediseñar el lenguaje (y, si uno se siente bien al respecto, existen excelentes soluciones holísticas como el TypeScript antes mencionado que mitigan la reinvención de la rueda)

Sin embargo, eso no quiere decir que la composición esté fuera de discusión en Plain Old JS. Investigué esto en profundidad hace algún tiempo. El candidato más fuerte que he visto para manejar la composición dentro del paradigma del prototipo de objeto es Stampit , que ahora uso en una amplia gama de proyectos. Y, lo que es más importante, se adhiere a una especificación bien articulada.

más información sobre sellos aquí

Jay Edwards
fuente
1
Mantengo mi puesto incluso con un -1. Lamentablemente, esa es la democracia de SO a veces. Espero que alguien encuentre útiles los enlaces. Stampit merece tu tiempo.
Jay Edwards
-1 no es un veredicto final. Tu publicación podría terminar con + 100 / -1. Sin embargo, sigo pensando que es vago. JS ya no está "libre de clases". Sospecho que la mayoría no entenderá que "composición clásica" significa lo que usted quiso decir: herencia. (Considere toda la herencia frente a la composición de la guerra santa.) Tampoco está claro qué es "Plain Old JS". ES5? Aunque con una sintaxis más detallada, admitía técnicas que están más extendidas ahora, como las mezclas "verdaderas" . Los sellos se ven interesantes, ¿cuáles son sus ventajas sobre las mezclas?
ᆼ ᆺ ᆼ
la palabra clave de clase es azúcar sintáctica. JS - ES ^ 6 o de otro tipo - no es un lenguaje de clase. simplemente decora el enfoque tradicional del constructor de funciones en ES5. "Simple y antiguo JS" define felizmente cualquiera de las implementaciones JS de ES, por lo tanto. Francamente, desearía que no se hubiera tomado la decisión de afianzar aún más la idea de clase en el lenguaje quora.com/Are-ES6-classes-bad-for-JavaScript. Los sellos reflejan mejor las fortalezas de JS en mi humilde opinión. stampit.js.org ofrece un buen resumen de las diferencias con las clases. En última instancia, es una metodología más pragmática.
Jay Edwards
1
Pero entonces, ¿qué es un "lenguaje de clase" ? C ++? classes solo un sinónimo de struct. ¿Un lenguaje verdaderamente clásico como Smalltalk? Se permite la extensión dinámica de prototipos y casos incluso
ᆼ ᆺ ᆼ
Ese es un punto razonable. Definiría un lenguaje de clase como un lenguaje intrínsecamente orientado a objetos. De MDN: "JavaScript es un lenguaje dinámico basado en prototipos, de múltiples paradigmas, que admite estilos orientados a objetos, imperativos y declarativos (por ejemplo, programación funcional)". google.com/url?sa=t&source=web&rct=j&url=https://…
Jay Edwards
6

Esta es mi solución al problema. Puede 'implementar' múltiples interfaces anulando una interfaz con otra.

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'

EDITAR:

Mejoré el código para que ahora pueda simplemente usar implement (baseClass, interface1, interface2, ...) en el archivo extend.

/**
* 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
fuente
0

hay paquetes que pueden simular interfaces.

puedes usar la interfaz es6

Amit Wagner
fuente
2
el problema con tu respuesta es que no pidió una "herramienta" para hacerlo. pero cómo se hizo, queni habría sido una respuesta más correcta que explicaría cómo lo hizo esa forma.
Gianfrancesco Aurecchia