ค้นหาเว็บไซต์

javascript - มีวิธีสร้างอินเตอร์เฟซใน ES6/Node 4 หรือไม่?


ES6 สามารถใช้งานได้เต็มที่ใน Node 4 ฉันสงสัยว่ามันรวมแนวคิดของอินเทอร์เฟซเพื่อกำหนดสัญญาของเมธอดเหมือนใน MyClass implements MyInterface หรือไม่

ฉันไม่สามารถหาข้อมูลได้มากจากการค้นหาใน Google ของฉัน แต่บางทีอาจมีเคล็ดลับหรือตัวแก้ปัญหาที่ดีที่มีอยู่

คำตอบ #1

อินเตอร์เฟซไม่ใช่ส่วนหนึ่งของ ES6 แต่คลาสเป็นส่วนหนึ่ง

ถ้าคุณต้องการพวกมันจริง ๆ คุณควรดูที่ TypeScript ซึ่งรองรับพวกมัน

คำตอบ #2

นี่คือวิธีแก้ปัญหาของฉันสำหรับปัญหานี้ คุณสามารถ 'ใช้งาน' อินเทอร์เฟซหลายตัวโดยการแทนที่อินเทอร์เฟซหนึ่งด้วยอีกอินเทอร์เฟซหนึ่ง

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'

แก้ไข:

ฉันได้ปรับปรุงโค้ดแล้ว ดังนั้นคุณสามารถใช้ implement(baseClass, interface1, interface2, ...) ใน 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'

คำตอบ #3

ในคอมเมนต์ debiasej เขียนว่าบทความที่กล่าวถึงด้านล่างนี้อธิบายเพิ่มเติมเกี่ยวกับรูปแบบการออกแบบ (อิงตามอินเทอร์เฟซและคลาส):

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

หนังสือรูปแบบการออกแบบในจาวาสคริปต์อาจมีประโยชน์สำหรับคุณด้วย:

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 );

คำตอบ #4

เนื่องจาก ECMA เป็นภาษาที่ 'ไม่มีคลาส' การนำการจัด composition แบบคลาสสิกมาใช้นั้น - ในสายตาของฉัน - ไม่ค่อยสมเหตุสมผลนัก อันตรายก็คือ การทำเช่นนั้น คุณกำลังพยายามสร้างภาษานี้ขึ้นมาใหม่อย่างมีประสิทธิภาพ (และถ้าหากใครรู้สึกจริงจังกับเรื่องนั้น ก็มีวิธีแก้ปัญหาแบบองค์รวมที่ยอดเยี่ยม เช่น TypeScript ที่กล่าวถึงข้างต้น ซึ่งช่วยลดการสร้างสิ่งที่มีอยู่แล้วซ้ำ)

นั่นไม่ได้หมายความว่าการประกอบเป็นสิ่งที่เป็นไปไม่ได้ใน Plain Old JS อย่างไรก็ตาม ผมได้วิจัยเรื่องนี้อย่างละเอียดสักพักหนึ่งแล้ว ผู้สมัครที่แข็งแกร่งที่สุดที่ผมเคยเห็นสำหรับการจัดการการประกอบภายในวัตถุแบบ prototypal คือ stampit ซึ่งตอนนี้ผมใช้มันในโครงการหลากหลายประเภท และที่สำคัญ มันปฏิบัติตามมาตรฐานที่กำหนดไว้อย่างชัดเจน

ข้อมูลเพิ่มเติมเกี่ยวกับแสตมป์ที่นี่

บทความที่เกี่ยวข้อง