Есть ли способ создавать интерфейсы в ES6 / Node 4?

110

ES6 полностью доступен в Node 4. Мне было интересно, включает ли он концепцию интерфейса для определения контрактов методов, как в MyClass implements MyInterface.

Я не могу найти много с помощью Google, но, возможно, есть хороший трюк или обходной путь.

Жером Верстринж
источник
2
В полной мере? Конечно, нет.
Берги
1
JS по-прежнему использует утиную печать . Статически принудительных «контрактов на методы» не существует. Если вы хотите протестировать их динамически, вы легко можете написать свою собственную программу проверки интерфейса.
Берги
26
Поздно на вечеринку, но не согласен, вопрос не по теме. OP требует подтверждения, существует ли ожидаемая функция. Новый, упрощенный синтаксис для классов давно назрел и, вероятно, будет широко использоваться. Но интерфейсы распространены в других языках по очень веской причине. Я тоже был удивлен и разочарован, узнав, что интерфейсы не являются частью ES2015. Учитывая, что это, вероятно, обычное открытие, IMHO небезосновательно спросить, есть ли предлагаемый обходной путь.
9
Какого черта это не по теме? Интерфейсы - это техника программирования, а не продукт. Вопрос верный и хороший, учитывая, что в выпуске ECMA Script 6 появились определения классов, подобные Java. Я думаю, что закрытие этой темы демонстрирует непонимание и то, как при переполнении стека система баллов не коррелирует со способностями.
Эндрю С.
4
Буквально ни в какой момент OP не просит нас порекомендовать или найти книгу, инструмент, библиотеку программного обеспечения, учебное пособие или другой сторонний ресурс по любому из этих вопросов.
Лиам

Ответы:

90

Интерфейсы не являются частью ES6, но классы являются.

Если они вам действительно нужны, обратите внимание на TypeScript, который их поддерживает .

Гельгиллард
источник
1
«они» - это интерфейсы. FWIW Возможно, вам придется внимательно изучить ссылку на транспиллер, приведенную выше. Не совсем так, как я ожидал, но близко.
Примечание: насколько мне известно, чистый интерфейс в TypeScript ни к чему не приводит. Только если вы их используете, то транспилированный код имеет определенную логику.
Даниэль Даниелецки,
9

В комментариях debiasej написал упомянутую ниже статью, в которой подробно рассказывается о шаблонах проектирования (на основе интерфейсов, классов):

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

Книга шаблонов проектирования на javascript также может быть вам полезна:

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

Шаблон проектирования = классы + интерфейс или множественное наследование

Пример фабричного шаблона в ES6 JS (для запуска: 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
источник
34
Так где же здесь интерфейс, который я могу составить с другими?
Дмитрий Зайцев
Более
2
На этом сайте есть отличное обновление шаблонов ES5 для ES6: loredanacirstea.github.io/es6-design-patterns
debiasej
8

Учитывая, что ECMA - это «бесклассовый» язык, реализация классической композиции, на мой взгляд, не имеет большого смысла. Опасность заключается в том, что, поступая таким образом, вы эффективно пытаетесь реорганизовать язык (и, если кто-то твердо убежден в этом, есть отличные целостные решения, такие как вышеупомянутый TypeScript, которые смягчают необходимость изобретать колесо)

Это не означает, что в Plain Old JS о композиции не может быть и речи. Некоторое время назад я подробно исследовал это. Самый сильный кандидат, который я видел для обработки композиции в рамках парадигмы прототипа объекта, - это stampit. , который я сейчас использую в широком спектре проектов. И, что немаловажно, он соответствует четко сформулированной спецификации.

подробнее о марках здесь

Джей Эдвардс
источник
1
Я стою на своем посту даже с -1. К сожалению, иногда так бывает с демократией ТАК. Надеюсь, ссылки кому-то пригодятся. Stampit стоит вашего времени.
Джей Эдвардс
-1 - это не окончательный вердикт. Ваш пост может получить + 100 / -1. Однако я все еще думаю, что это расплывчато. JS больше не является "свободным от классов". Я подозреваю, что «классическая композиция» также не будет пониматься большинством как означающее то, что вы имели в виду: наследование. (Рассмотрим всю священную войну между наследованием и композицией.) Также неясно, что такое «Plain Old JS». ES5? Несмотря на то, что синтаксис был более подробным, он поддерживал более распространенные сейчас методы, такие как «настоящие» микшеры . Марки выглядят интересно, в чем их преимущества перед миксами?
ᆼ ᆺ ᆼ
ключевое слово class - это синтаксический сахар. JS - ES ^ 6 или иначе - не является языком классов. он просто украшает традиционный подход к конструктору функций в ES5. Поэтому "старый добрый JS" с радостью определяет любую из JS-реализаций ES. Честно говоря, мне жаль, что не было принято решение о дальнейшем закреплении идеи класса в языке quora.com/Are-ES6-classes-bad-for-JavaScript. ИМХО, марки лучше отражают сильные стороны JS. stampit.js.org дает хорошее изложение отличий от классов. В конечном счете, это более прагматичная методология.
Джей Эдвардс
1
Но тогда что такое «классовый язык» ? C ++? classэто просто синоним struct. Действительно классический язык вроде Smalltalk? Он позволяет динамически расширять прототипы и даже экземпляры
ᆼ ᆺ ᆼ
Это разумный аргумент. Я бы определил язык классов как язык, который по своей сути является ООП. Из MDN: «JavaScript - это основанный на прототипах, мультипарадигмальный, динамический язык, поддерживающий объектно-ориентированные, императивные и декларативные стили (например, функциональное программирование)». google.com/url?sa=t&source=web&rct=j&url=https://…
Джей Эдвардс,
6

Это мое решение проблемы. Вы можете «реализовать» несколько интерфейсов, заменив один интерфейс другим.

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'

РЕДАКТИРОВАТЬ:

Я улучшил код, так что теперь вы можете просто использовать реализацию (baseClass, interface1, interface2, ...) в расширении.

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

есть пакеты, которые могут моделировать интерфейсы.

вы можете использовать es6-интерфейс

Амит Вагнер
источник
2
проблема с вашим ответом в том, что он не просил «инструмент» для этого. но как это было сделано, queni было бы более правильным ответом, который объяснял бы, как эта форма сделала это.
Джанфранческо Ауреккья