Чи є спосіб створити інтерфейси в ES6 / Node 4?


110

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

Я не можу знайти багато з моїм Googling, але, можливо, є приємний трюк або спосіб вирішення.


2
Повністю? Поки що ні.
Бергі

1
JS як і раніше використовує типи качок . Не існує статистично закріплених "методологічних договорів". Якщо ви хочете динамічно протестувати їх, ви легко можете написати свою перевірку інтерфейсу.
Бергі

26
Пізно до партії, але не погоджуйтесь, це питання поза темою. ОП хоче підтвердження, якщо очікувана функція існує. Новий, спрощений, синтаксис для класів давно назрів і, ймовірно, буде широко використовуватися. Але інтерфейси поширені в інших мовах з дуже вагомих причин. Я теж був здивований і розчарований тим, що вивчати інтерфейси не є частиною ES2015. Зважаючи на те, що це, ймовірно, є загальним відкриттям, ІМХО нерозумно запитувати, чи є запропоноване рішення.

9
Як на землі це поза темою? Інтерфейси - це техніка програмування, а не продукт. Питання справедливе і є гарним, коли випуск ECMA Script 6 приносить Java, як визначення класу. Я думаю, що закриття цієї теми демонструє нерозуміння та те, як на Stack переповнення система балів не співвідноситься зі здібностями.
Ендрю S

4
В буквальному сенсі ОП (просить) не рекомендувати або знаходити книгу, інструмент, бібліотеку програмного забезпечення, навчальний посібник чи інший ресурс за межами сайту в будь-якому з цього питання.
Ліам

Відповіді:


90

Інтерфейси не є частиною ES6, але класи є.

Якщо вони вам справді потрібні, варто подивитися на TypeScript, який їх підтримує .


1
"вони" є інтерфейсами. FWIW Можливо, вам потрібно буде уважно розглянути посилання для транспілятора, передбачене вище. Не зовсім так, як я очікував, але близько.

Примітка: наскільки мені відомо, чистий інтерфейс у TypeScript перетворюється ні до чого. Тільки якщо ви їх використовуєте, тоді перекладений код має певну логіку.
Даніель Данієлецький

9

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

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

Книга шаблонів дизайну в JavaScript може також бути корисною для вас:

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

Шаблон дизайну = класи + інтерфейс або багатократне успадкування

Приклад заводського шаблону в ES6 JS (для запуску: вузол 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
То де тут інтерфейс, який я можу скласти з іншими?
Дмитро Зайцев

Деякі більш глибокі пояснення є на цьому веб-сайті: sitepoint.com/object-oriented-javascript-deep-dive-es6-classes
42n4

2
На цьому сайті є велике оновлення моделей ES5 до ES6: loredanacirstea.github.io/es6-design-patterns
debiasej

8

Зважаючи на те, що ECMA - це «безкласна» мова, реалізація класичної композиції - на мої очі - не має великого сенсу. Небезпека полягає в тому, що, роблячи це, ви ефективно намагаєтесь перепрофілювати мову (і, якщо хтось із цим сильно відчуває, є чудові цілісні рішення, наприклад, згаданий вище TypeScript, який зменшує винахід колеса)

Тепер це не означає, що в Plain Old JS композиція не викликає сумніву. Я досліджував це докладно деякий час тому. Найсильніший кандидат, який я бачив для обробки композиції в рамках об'єктної прототипічної парадигми, є штамп , який я зараз використовую в широкому спектрі проектів. І, що важливо, він дотримується чітко сформульованої специфікації.

Більше інформації про марки тут


1
Я стою біля своєї посади навіть з -1. На жаль, іноді це демократія SO. Я сподіваюся, що хтось вважає посилання корисними. Stampit вартий вашого часу.
Джей Едвардс

-1 не є остаточним вердиктом. Ваша публікація може закінчитися + 100 / -1. Однак я все ще думаю, що це розпливчасто. JS вже не є "класним". Я підозрюю, що "Класична композиція" також не буде зрозумілою більшості для того, що ви мали на увазі: спадщину. (Розглянемо всю спадщину проти складу святої війни.) Також не ясно, що таке "Звичайний Старий ЖС". ES5? Хоча і з більш багатослівним синтаксисом, він підтримував більш широко розповсюджені в даний час методи, такі як "справжні" мікси . Марки виглядають цікаво, у чому їх переваги перед мікшнами?
ᆼ ᆺ ᆼ

ключове слово класу - синтаксичний цукор. JS - ES ^ 6 або інше - не є мовою класу. він просто прикрашає традиційний підхід конструктора функцій у ES5. Отже, "звичайний старий JS" з радістю визначає будь-яку з JS-реалізацій ES. Чесно кажучи, я б хотів, щоб не було прийнято рішення про подальше закріплення ідеї класу на мові quora.com/Are-ES6-classes-bad-for-JavaScript. Марки краще відображають сильні сторони ІМХО. stampit.js.org дає змогу добре відзначити відмінності від класів. Зрештою, це більш прагматична методологія.
Джей Едвардс

1
Але тоді, що таке "класна мова" ? C ++? class- це просто синонім для struct. Справді класична мова, як Smalltalk? Це дозволяє динамічно розширювати прототипи та навіть екземпляри
ᆼ ᆺ ᆼ

Це розумний момент. Я б визначив мову класу як мову, яка по суті є OOP. Від 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'

Редагувати:

Я вдосконалив код, так що тепер ви можете просто використовувати Implementa (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
проблема вашої відповіді полягає в тому, що він не попросив "інструменту" для цього. але як це було зроблено кені, правильнішою була б відповідь, яка б пояснювала, як це робило це.
Gianfrancesco Aurecchia
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.