Czy istnieje sposób na tworzenie interfejsów w ES6 / Node 4?

110

ES6 jest w pełni dostępne w Node 4. Zastanawiałem się, czy zawiera koncepcję interfejsu do definiowania kontraktów metod, jak w MyClass implements MyInterface.

Nie mogę znaleźć zbyt wiele w Google, ale może jest dostępna fajna sztuczka lub obejście.

Jérôme Verstrynge
źródło
2
Całkowicie? Zdecydowanie nie.
Bergi
1
JS nadal używa pisania typu duck . Nie ma statycznie wymuszanych „umów dotyczących metod”. Jeśli chcesz przetestować je dynamicznie, możesz łatwo napisać własny program do sprawdzania interfejsu.
Bergi
26
Spóźniłem się na imprezę, ale nie zgadzam się, pytanie jest nie na temat. OP chce potwierdzenia, jeśli istnieje oczekiwana funkcja. Nowa, uproszczona składnia klas jest spóźniona i prawdopodobnie będzie szeroko stosowana. Ale interfejsy są powszechne w innych językach nie bez powodu. Ja również byłem zaskoczony i rozczarowany, gdy dowiedziałem się, że interfejsy nie są częścią ES2015. Biorąc pod uwagę, że jest to prawdopodobnie powszechne odkrycie, IMHO nie jest nierozsądne pytanie, czy istnieje sugerowane obejście tego problemu.
9
Jak to na ziemi jest poza tematem? Interfejsy to technika programowania, a nie produkt. Pytanie jest poprawne i dobre w wydaniu ECMA Script 6 zawierającym definicje klas podobne do Javy. Myślę, że zamknięcie tego tematu pokazuje brak zrozumienia i jak przy przepełnieniu stosu system punktów nie koreluje ze zdolnościami.
Andrew S,
4
Dosłownie w żadnym momencie OP (nie prosi) nas o polecenie lub znalezienie książki, narzędzia, biblioteki oprogramowania, samouczka ani innych zasobów zewnętrznych w żadnym z tych pytań.
Liam,

Odpowiedzi:

90

Interfejsy nie są częścią ES6, ale klasy są.

Jeśli naprawdę ich potrzebujesz, powinieneś spojrzeć na TypeScript, który je obsługuje .

gaelgillard
źródło
1
„oni” są interfejsami. FWIW Konieczne może być uważne rozważenie łącza do transpilera podanego powyżej. Nie dokładnie tak, jak się spodziewałem, ale blisko.
Uwaga: o ile wiem, czysty interfejs w TypeScript przenosi się do niczego. Tylko jeśli ich użyjesz, przetransponowany kod ma określoną logikę.
Daniel Danielecki
9

W komentarzach debiasej napisał wspomniany poniżej artykuł, który wyjaśnia więcej na temat wzorców projektowych (opartych na interfejsach, klasach):

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

Książka wzorców projektowych w javascript może Ci się również przydać:

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

Wzorzec projektowy = klasy + interfejs lub dziedziczenie wielokrotne

Przykład wzorca fabrycznego w ES6 JS (do uruchomienia: 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
źródło
34
Więc gdzie jest tutaj interfejs, który mogę komponować z innymi?
Dmitri Zaitsev
Niektóre głębsze wyjaśnienia znajdują się na tej stronie: sitepoint.com/object-oriented-javascript-deep-dive-es6-classes
42n4
2
Na tej stronie znajduje się świetna aktualizacja wzorców ES5 do ES6: loredanacirstea.github.io/es6-design-patterns
debiasej
8

Biorąc pod uwagę, że ECMA jest językiem „bezklasowym”, wdrażanie klasycznej kompozycji nie ma - moim zdaniem - sensu. Niebezpieczeństwo polega na tym, że robiąc to, skutecznie próbujesz przeprojektować język (i, jeśli ktoś jest tego mocno przekonany, istnieją doskonałe holistyczne rozwiązania, takie jak wspomniany wcześniej TypeScript, które łagodzą ponowne wymyślanie koła)

Nie oznacza to jednak, że kompozycja nie wchodzi w rachubę w Plain Old JS. Długo to badałem jakiś czas temu. Najsilniejszym kandydatem, jakiego widziałem do radzenia sobie z kompozycją w ramach prototypowego paradygmatu obiektu, jest stampit , którego obecnie używam w wielu różnych projektach. I, co ważne, jest zgodny z dobrze sformułowaną specyfikacją.

więcej informacji o znaczkach tutaj

Jay Edwards
źródło
1
Stoję przy swoim stanowisku nawet z -1. Niestety, taka jest czasami demokracja SO. Mam nadzieję, że komuś linki okażą się przydatne. Stampit jest wart twojego czasu.
Jay Edwards
-1 nie jest ostatecznym werdyktem. Twój post może skończyć się + 100 / -1. Jednak nadal uważam, że jest to niejasne. JS nie jest już „wolny od klas”. Podejrzewam, że większość osób nie będzie rozumiała tego, co miałeś na myśli: „Kompozycja klasyczna”. (Rozważmy całe dziedzictwo vs. kompozycja świętej wojny). Nie jest też jasne, czym jest „Zwykły Stary JS”. ES5? Chociaż z bardziej szczegółową składnią, wspierał techniki, które są obecnie bardziej rozpowszechnione, takie jak „prawdziwe” mieszanki . Znaczki ciekawie wyglądają, jakie są ich zalety w stosunku do miksów?
ᆼ ᆺ ᆼ
słowem kluczowym class jest cukier syntaktyczny. JS - ES ^ 6 lub inaczej - nie jest językiem klasowym. po prostu zdobi tradycyjne podejście konstruktora funkcji w ES5. Dlatego „zwykły stary JS” szczęśliwie definiuje każdą implementację JS w ES. Szczerze mówiąc, żałuję, że nie podjęto decyzji o dalszym utrwalaniu idei klasy w języku quora.com/Are-ES6-classes-bad-for-JavaScript. Znaczki lepiej odzwierciedlają mocne strony JS IMHO. W stampit.js.org znajduje się dobry przegląd różnic między klasami. Ostatecznie jest to bardziej pragmatyczna metodologia.
Jay Edwards
1
Ale czym jest „język klasowy” ? C ++? classjest tylko synonimem struct. Naprawdę klasyczny język, taki jak Smalltalk? To pozwala na dynamiczne rozszerzenie prototypów i nawet przypadkach
ᆼ ᆺ ᆼ
To rozsądny punkt. Zdefiniowałbym język klasowy jako język z natury rzeczy OOP. Z MDN: „JavaScript to oparty na prototypach, wieloparadygmatyczny, dynamiczny język, obsługujący style obiektowe, imperatywne i deklaratywne (np. Programowanie funkcjonalne)”. google.com/url?sa=t&source=web&rct=j&url=https://…
Jay Edwards
6

To jest moje rozwiązanie problemu. Możesz „zaimplementować” wiele interfejsów, zastępując jeden interfejs innym.

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'

EDYTOWAĆ:

Poprawiłem kod, dzięki czemu możesz teraz po prostu użyć implement (baseClass, interface1, interface2, ...) w rozszerzeniu.

/**
* 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
źródło
0

istnieją pakiety, które mogą symulować interfejsy.

możesz użyć interfejsu es6

Amit Wagner
źródło
2
problem z twoją odpowiedzią polega na tym, że nie prosił on o „narzędzie” do tego. ale jak to zostało zrobione queni byłaby bardziej poprawną odpowiedzią, która wyjaśniałaby, jak ta forma tego dokonała.
Gianfrancesco Aurecchia,