Existe uma maneira de criar interfaces no ES6 / Nó 4?


109

ES6 está totalmente disponível no Nó 4. Eu queria saber se ele inclui um conceito de interface para definir contratos de método como em MyClass implements MyInterface.

Não consigo encontrar muito com meu Google, mas talvez haja um bom truque ou solução alternativa disponível.


2
Totalmente? De longe não.
Bergi,

1
JS ainda usa digitação de pato . Não existem "contratos de método" impostos estaticamente. Se quiser testá-los dinamicamente, você pode facilmente escrever seu próprio verificador de interface.
Bergi,

26
Atrasado para a festa, mas discordo, a questão está fora do assunto. OP deseja confirmação se um recurso esperado existe. A nova sintaxe simplificada para classes está muito atrasada e provavelmente será amplamente utilizada. Mas as interfaces são comuns em outros idiomas por um bom motivo. Eu também fiquei surpreso e desapontado ao saber que interfaces não fazem parte do ES2015. Dado que esta é provavelmente uma descoberta comum, IMHO não é irracional perguntar se existe uma solução alternativa sugerida.

9
Como diabos isso está fora do assunto? As interfaces são técnicas de programação, não um produto. A questão é válida e é boa com o lançamento do ECMA Script 6 trazendo definições de classe semelhantes ao Java. Acho que o encerramento deste tópico demonstra a falta de compreensão e como no estouro do Stack o sistema de pontos não se correlaciona com a habilidade.
Andrew S

4
Literalmente, em nenhum momento o OP (nos pede) para recomendar ou encontrar um livro, ferramenta, biblioteca de software, tutorial ou outro recurso externo em qualquer uma dessas questões.
Liam

Respostas:


89

As interfaces não fazem parte do ES6, mas as classes sim.

Se você realmente precisa deles, deve olhar o TypeScript que os suporta .


1
"eles" sendo interfaces. FWIW Você pode precisar considerar cuidadosamente o link para o transpiler fornecido acima. Não exatamente como eu esperava, mas perto.

Uma nota: até onde eu sei, a interface pura em TypeScript se transforma em nada. Somente se você usá-los, o código transpilado terá uma certa lógica.
Daniel Danielecki

9

Nos comentários, debiasej escreveu o artigo abaixo mencionado que explica mais sobre os padrões de design (com base em interfaces, classes):

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

O livro de padrões de design em javascript também pode ser útil para você:

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

Padrão de design = classes + interface ou herança múltipla

Um exemplo do padrão de fábrica em ES6 JS (para executar: 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
Então, onde está aqui uma interface que eu possa compor com outras pessoas?
Dmitri Zaitsev

Algumas explicações mais
detalhadas

2
Há uma grande atualização dos padrões ES5 para ES6 neste site: loredanacirstea.github.io/es6-design-patterns
debiasej

8

Dado que ECMA é uma linguagem 'livre de classes', implementar a composição clássica não faz muito sentido - a meu ver. O perigo é que, ao fazê-lo, você está efetivamente tentando reengenharia da linguagem (e, se alguém tiver essa convicção, existem excelentes soluções holísticas, como o TypeScript mencionado anteriormente, que atenua a reinvenção da roda)

Agora, isso não quer dizer que a composição esteja fora de questão, no entanto, em Plain Old JS. Pesquisei longamente sobre isso há algum tempo. O candidato mais forte que vi para lidar com a composição dentro do paradigma prototípico do objeto é stampit , que agora uso em uma ampla gama de projetos. E, mais importante, segue uma especificação bem articulada.

mais informações sobre selos aqui


1
Eu mantenho minha posição mesmo com -1. Infelizmente, às vezes essa é a democracia do SO. Espero que alguém ache os links úteis. Stampit vale o seu tempo.
Jay Edwards

-1 não é um veredicto final. Sua postagem pode terminar em + 100 / -1. No entanto, ainda acho que é vago. JS não é mais "livre de classes". Suspeito que "composição clássica" também não será entendida pela maioria como significando o que você quis dizer: herança. (Considere toda a herança versus composição da guerra santa.) Também não está claro o que é "Plain Old JS". ES5? Embora com uma sintaxe mais detalhada, ele suportava técnicas que estão mais difundidas agora, como mix-ins "verdadeiros" . Os selos parecem interessantes. Quais são suas vantagens sobre os mix-ins?
ᆼ ᆺ ᆼ

a palavra-chave da classe é açúcar sintático. JS - ES ^ 6 ou outro - não é uma linguagem de classe. ele meramente decora a abordagem tradicional do construtor de função no ES5. "plain old JS" felizmente define qualquer uma das implementações JS do ES, portanto. Francamente, gostaria que a decisão não tivesse sido tomada para consolidar ainda mais a ideia de classe na linguagem quora.com/Are-ES6-classes-bad-for-JavaScript. Os selos refletem melhor os pontos fortes de JS, IMHO. stampit.js.org fornece um bom resumo das diferenças das classes. Em última análise, é uma metodologia mais pragmática.
Jay Edwards

1
Mas então, o que é uma "linguagem de classe" ? C ++? classé apenas um sinônimo para struct. Uma linguagem verdadeiramente clássica como Smalltalk? Ele permite a extensão dinâmica de protótipos e até mesmo instâncias
ᆼ ᆺ ᆼ

Esse é um ponto razoável. Eu definiria uma linguagem de classe como uma linguagem que é intrinsecamente OOP. Do MDN: "JavaScript é uma linguagem dinâmica, multiparadigma, baseada em protótipo, com suporte a estilos orientados a objetos, imperativos e declarativos (por exemplo, programação funcional)." google.com/url?sa=t&source=web&rct=j&url=https://…
Jay Edwards

6

Esta é a minha solução para o problema. Você pode 'implementar' várias interfaces substituindo uma interface por outra.

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:

Eu melhorei o código para que agora você possa simplesmente usar implementar (baseClass, interface1, interface2, ...) na extensão.

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

existem pacotes que podem simular interfaces.

você pode usar a interface es6


2
o problema com sua resposta é que ele não pediu uma "ferramenta" para fazer isso. mas como foi feito o queni teria sido mais correto uma resposta que explicaria como aquela forma o fez.
Gianfrancesco Aurecchia
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.