126

ES6 is fully available in Node 4. I was wondering whether it includes a concept of interface to define method contracts as in MyClass implements MyInterface.

I can't find much with my Googling, but maybe there is a nice trick or workaround available.

Jérôme Verstrynge
  • 57,710
  • 92
  • 283
  • 453
  • 2
    Fully? [By far not.](https://kangax.github.io/compat-table/es6/) – Bergi Sep 17 '15 at 13:42
  • 1
    JS still uses [duck typing](https://en.wikipedia.org/wiki/Duck_typing). There are no statically enforced "method contracts". If you want to test them dynamically, you easily can write your own interface checker. – Bergi Sep 17 '15 at 13:44
  • There are already quite a few books available on ES6, e.g. [this one](https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/README.md#you-dont-know-js-es6--beyond). If you read one of them you won't have to wonder anymore what feature is and isn't available in ES6. In the worst, case [look at the spec](http://www.ecma-international.org/ecma-262/6.0/index.html). The term "interface" appears 12 times. Please don't create a question for every feature that a language could possibly have. – Felix Kling Sep 17 '15 at 13:57
  • 33
    Late to the party, but disagree the question is off-topic. OP wants confirmation if an expected feature exists. The new, simplified, syntax for classes is long overdue and will likely be widely used. But interfaces are common in other languages for very good reason. I too was surprised, and disappointed, to learn interfaces are not part of ES2015. Given that this is likely a common discovery, IMHO it is not unreasonable to ask if there is a suggested workaround. –  Mar 18 '17 at 20:57
  • 10
    How on earth is this off topic? Interfaces are programming technique not a product. The question is valid and is a good one with the release of ECMA Script 6 bringing in Java like class definitions. I think the closing of this topic demonstrates the lack of understanding and how on Stack overflow the points system does not correlate with ability. – Andrew S Jan 08 '18 at 06:11
  • 4
    At literally no point does the OP *(ask) us to recommend or find a book, tool, software library, tutorial or other off-site resource* in any of this question. – Liam Feb 09 '18 at 16:11
  • It's not nice to give for granted that any developer in the word has money to buy books; there's a lot of entitlement in that comment. Especially in the context of internet and free/cheap access to information. Especially in a supportive community like SO. – It looks like the question is bothering the commenter; that, btw, doesn't even really answer/help and it even wrongly induce the OP in thinking that interfaces in ES6 exist, just because the word is recurrent. – Many end up here with the same legit doubt (see votes and stars); this answer will be relevant till ES won't have interfaces. – Kamafeather May 19 '19 at 17:22
  • Its possible with the extend keyword from a class. Example create an interface as class with empty methods. Override these methods in extended classes because methods can be overriden. In an an interface all methods must be implementerend. Not in these classes, so they behave more like abstract classes but its as close as it gets to interfaces. – Herman Van Der Blom Mar 08 '21 at 10:08

6 Answers6

107

Interfaces are not part of the ES6 but classes are.

If you really need them, you should look at TypeScript which support them.

Ninjakannon
  • 3,751
  • 7
  • 53
  • 76
gaelgillard
  • 2,483
  • 1
  • 14
  • 20
  • 1
    "them" being interfaces. FWIW You may need to consider carefully the link for the transpiler provided above. Not exactly as I expected, but close. –  Mar 18 '17 at 22:14
  • A note: as far as I'm aware pure interface in TypeScript transpiles to nothing. Only if you use them then the transpiled code has a certain logic. – Daniel Danielecki Jun 03 '20 at 16:54
  • I want to note that ES6 classes are in fact not real classes but just a shortcut for multiple things you would otherwise do manually like Object.create() and Object.setPrototypeOf(). JS is still a prototype based language. – Kai Lehmann Nov 25 '20 at 17:30
16

This is my solution for the problem. You can 'implement' multiple interfaces by overriding one Interface with another.

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'

EDIT:

I improved the code so you now can simply use implement(baseClass, interface1, interface2, ...) in the extends.

/**
 * 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'
Teocci
  • 7,189
  • 1
  • 50
  • 48
Kai Lehmann
  • 508
  • 4
  • 17
10

In comments debiasej wrote the mentioned below article explains more about design patterns (based on interfaces, classes):

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

Design patterns book in javascript may also be useful for you:

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

Design pattern = classes + interface or multiple inheritance

An example of the factory pattern in ES6 JS (to run: 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
  • 1,292
  • 22
  • 26
10

Given that ECMA is a 'class-free' language, implementing classical composition doesn't - in my eyes - make a lot of sense. The danger is that, in so doing, you are effectively attempting to re-engineer the language (and, if one feels strongly about that, there are excellent holistic solutions such as the aforementioned TypeScript that mitigate reinventing the wheel)

Now that isn't to say that composition is out of the question however in Plain Old JS. I researched this at length some time ago. The strongest candidate I have seen for handling composition within the object prototypal paradigm is stampit, which I now use across a wide range of projects. And, importantly, it adheres to a well articulated specification.

more information on stamps here

Jay Edwards
  • 950
  • 1
  • 12
  • 21
  • 1
    I stand by my post even with a -1. Sadly, that is the democracy of SO sometimes. I hope someone finds the links useful. Stampit is worth your time. – Jay Edwards May 01 '19 at 04:10
  • -1 is not a final verdict. Your post might end up +100/-1. However I still think it's vague. JS is not "class-free" anymore. I suspect "Classical composition" will also not be understood by most to mean what you meant: inheritance. (Consider the whole inheritance vs. composition holy war.) It is also not clear what "Plain Old JS" is. ES5? Albeit with a more verbose syntax, it supported techniques that are more widespread now, such as ["true" mix-ins](//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Mix-ins). Stamps look interesting, what are their advantages over mix-ins? – P Varga May 01 '19 at 04:44
  • the class keyword is syntactic sugar. JS - ES^6 or otherwise - is not a class language. it merely decorates the traditional function constructor approach in ES5. "plain old JS" happily defines any of the JS implementations of ES, therefore. Frankly, I wish the decision hadn't been made to further entrench the idea of class in the language quora.com/Are-ES6-classes-bad-for-JavaScript. Stamps better reflect JS' strengths IMHO. https://stampit.js.org/ gives a good rundown of the differences from classes. Ultimately, it's a more pragmatic methodology. – Jay Edwards May 01 '19 at 05:53
  • 2
    But then, what is a *"class language"*? C++? `class` is just a synonym for `struct`. A truly classic language like Smalltalk? It [allows dynamic extension of prototypes and even instances](https://stackoverflow.com/a/14202534/579078) – P Varga May 01 '19 at 06:18
  • That's a reasonable point. I would define a class language as a language that is intrinsically OOP. From MDN: "JavaScript is a prototype-based, multi-paradigm, dynamic language, supporting object-oriented, imperative, and declarative (e.g. functional programming) styles." https://www.google.com/url?sa=t&source=web&rct=j&url=https://developer.mozilla.org/en-US/docs/Web/JavaScript&ved=2ahUKEwi2i86UyPrhAhVRmVkKHc3_CyMQFjAHegQIDRAi&usg=AOvVaw1Il_CfTbNi4CXc-0nBN5rP – Jay Edwards May 01 '19 at 14:44
  • In fact classes in js are more than syntactic sugar. There is real inheritance by static methods what you dont have if you the prototype way. – Kai Lehmann May 23 '19 at 04:05
  • This from MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain `JavaScript is a bit confusing for developers experienced in class-based languages (like Java or C++), as it is dynamic and does not provide a class implementation per se (the class keyword is introduced in ES2015, but is syntactical sugar, JavaScript remains prototype-based).` – Jay Edwards Jul 11 '19 at 23:26
  • From the last paragraph of the MDN link: `...the prototypal inheritance model itself is, in fact, more powerful than the classic model. It is, for example, fairly trivial to build a classic model on top of a prototypal model.` – Liam Mar 05 '20 at 11:56
0

there are packages that can simulate interfaces .

you can use es6-interface

Amit Wagner
  • 3,134
  • 3
  • 19
  • 35
  • 3
    the problem with your answer is that he did not ask for a "tool" to do it. but how it was done queni would have been more correct an answer that would spell out how that form did it. – Gianfrancesco Aurecchia Feb 12 '19 at 08:08
0

Flow allows interface specification, without having to convert your whole code base to TypeScript.

Interfaces are a way of breaking dependencies, while stepping cautiously within existing code.

Engineer
  • 8,529
  • 7
  • 65
  • 105
  • 1
    You can implement abstract classes in javascript. With the extend keyword You can override these empty methods from the abstract classes. The only difference is that in interface all methods must be implemented. I call these abstract classes but its just a javscript class wich needs to be extended with the extend keyword. – Herman Van Der Blom Mar 08 '21 at 10:14