456 Stimmen

Verfügt JavaScript über einen Schnittstellentyp (wie z. B. Java's 'interface')?

Ich lerne wie man OOP mit JavaScript macht . Verfügt es über ein Schnittstellenkonzept (wie das von Java interface ) ?

Ich könnte also einen Hörer erstellen...

855voto

cHao Punkte 81921

Es gibt keine Vorstellung von "diese Klasse muss diese Funktionen haben" (d.h. keine Schnittstellen per se), denn:

  1. Die Vererbung in JavaScript basiert auf Objekten, nicht auf Klassen. Das ist keine große Sache, bis Sie merken:
  2. JavaScript ist ein extrem dynamisch typisierte Sprache - Sie können ein Objekt mit den richtigen Methoden erstellen, so dass es der Schnittstelle entspricht, und dann all die Dinge, die sie konform gemacht haben, wieder rückgängig machen . Es wäre so einfach, das Typensystem zu unterwandern - selbst aus Versehen! -- dass es sich nicht lohnen würde, überhaupt ein Typsystem zu entwickeln.

Stattdessen verwendet JavaScript das so genannte Duckmäuserschreiben . (Wenn es wie eine Ente läuft und wie eine Ente quakt, ist es für JS eine Ente.) Wenn Ihr Objekt über die Methoden quack(), walk() und fly() verfügt, kann der Code es überall dort verwenden, wo ein Objekt erwartet wird, das laufen, quaken und fliegen kann, ohne dass eine "Duckable"-Schnittstelle implementiert werden muss. Die Schnittstelle ist genau die Menge der Funktionen, die der Code verwendet (und die Rückgabewerte dieser Funktionen), und mit der Duck-Typisierung erhalten Sie das umsonst.

Das heißt aber nicht, dass Ihr Code nicht auf halbem Weg scheitern wird, wenn Sie versuchen, die some_dog.quack() ; Sie erhalten einen TypeError. Ehrlich gesagt, wenn Sie Hunden sagen, dass sie quaken sollen, haben Sie etwas größere Probleme; die Eingabe von Enten funktioniert am besten, wenn Sie sozusagen alle Ihre Enten in einer Reihe halten und Hunde und Enten nicht miteinander vermischen, es sei denn, Sie behandeln sie als generische Tiere. Mit anderen Worten: Auch wenn die Schnittstelle fließend ist, ist sie immer noch da; es ist oft ein Fehler, einen Hund an einen Code zu übergeben, der von ihm erwartet, dass er quakt und fliegt.

Wenn Sie aber sicher sind, dass Sie das Richtige tun, können Sie das Quacking-Dog-Problem umgehen, indem Sie die Existenz einer bestimmten Methode testen, bevor Sie versuchen, sie anzuwenden. Etwas wie

if (typeof(someObject.quack) == "function")
{
    // This thing can quack
}

So können Sie alle Methoden, die Sie verwenden können, überprüfen, bevor Sie sie einsetzen. Die Syntax ist allerdings etwas hässlich. Es gibt einen etwas hübscheren Weg:

Object.prototype.can = function(methodName)
{
     return ((typeof this[methodName]) == "function");
};

if (someObject.can("quack"))
{
    someObject.quack();
}

Es handelt sich um Standard-JavaScript, so dass es in jedem brauchbaren JS-Interpreter funktionieren sollte. Es hat den zusätzlichen Vorteil, dass es sich wie Englisch liest.

Bei modernen Browsern (d. h. bei so ziemlich jedem Browser außer IE 6-8) gibt es sogar eine Möglichkeit, die Eigenschaft nicht in for...in :

Object.defineProperty(Object.prototype, 'can', {
    enumerable: false,
    value: function(method) {
        return (typeof this[method] === 'function');
    }
}

Das Problem ist, dass IE7-Objekte nicht über .defineProperty überhaupt nicht, und im IE8 funktioniert es angeblich nur bei Host-Objekten (d. h. DOM-Elementen und dergleichen). Wenn Kompatibilität ein Thema ist, können Sie nicht verwenden .defineProperty . (Ich werde den IE6 nicht einmal erwähnen, da er außerhalb Chinas kaum noch eine Rolle spielt).

Ein weiteres Problem ist, dass einige Kodierungsstile davon ausgehen, dass jeder schlechten Code schreibt, und die Änderung von Object.prototype für den Fall, dass jemand blindlings die for...in . Wenn Ihnen das wichtig ist, oder Sie (IMO gebrochen ) Code, der dies tut, versuchen Sie eine etwas andere Version:

function can(obj, methodName)
{
     return ((typeof obj[methodName]) == "function");
}

if (can(someObject, "quack"))
{
    someObject.quack();
}

91voto

BGerrissen Punkte 20514

Holen Sie sich ein Exemplar von ' JavaScript-Entwurfsmuster ' von Dustin Diaz . Es gibt ein paar Kapitel, die der Implementierung von JavaScript-Schnittstellen durch Duck Typing gewidmet sind. Auch das ist eine schöne Lektüre. Aber nein, es gibt keine spracheigene Implementierung einer Schnittstelle, man muss Ente Typ .

// example duck typing method
var hasMethods = function(obj /*, method list as strings */){
    var i = 1, methodName;
    while((methodName = arguments[i++])){
        if(typeof obj[methodName] != 'function') {
            return false;
        }
    }
    return true;
}

// in your code
if(hasMethods(obj, 'quak', 'flapWings','waggle')) {
    //  IT'S A DUCK, do your duck thang
}

26voto

Steven de Salas Punkte 19903

JavaScript (ECMAScript Ausgabe 3) hat eine implements reserviertes Wort aufgespart für zukünftige Verwendung . Ich denke, dass dies genau für diesen Zweck gedacht ist, aber in der Eile, die Spezifikation herauszubringen, hatten sie keine Zeit zu definieren, was damit zu tun ist, also tun die Browser derzeit nichts anderes, als es dort zu lassen und sich gelegentlich zu beschweren, wenn man versucht, es für etwas zu verwenden.

Es ist möglich und in der Tat einfach genug, eine eigene Object.implement(Interface) Methode mit einer Logik, die sich wehrt, wenn ein bestimmter Satz von Eigenschaften/Funktionen in einem bestimmten Objekt nicht implementiert ist.

Ich habe einen Artikel geschrieben über Objektorientierung wobei ich meine eigene Notation wie folgt verwende :

// Create a 'Dog' class that inherits from 'Animal'
// and implements the 'Mammal' interface
var Dog = Object.extend(Animal, {
    constructor: function(name) {
        Dog.superClass.call(this, name);
    },
    bark: function() {
        alert('woof');
    }
}).implement(Mammal);

Es gibt viele Möglichkeiten, diese spezielle Katze zu häuten, aber dies ist die Logik, die ich für meine eigene Implementierung der Schnittstelle verwendet habe. Ich bevorzuge diesen Ansatz, und er ist einfach zu lesen und zu verwenden (wie Sie oben sehen können). Es bedeutet das Hinzufügen einer 'implement' Methode zu Function.prototype Manche Leute haben damit vielleicht ein Problem, aber ich finde, es funktioniert wunderbar.

Function.prototype.implement = function() {
    // Loop through each interface passed in and then check 
    // that its members are implemented in the context object (this).
    for(var i = 0; i < arguments.length; i++) {
       // .. Check member's logic ..
    }
    // Remember to return the class being tested
    return this;
}

15voto

Cody Punkte 9242

JavaScript-Schnittstellen:

Auch wenn JavaScript pas haben die interface Typs ist sie oft erforderlich. Aus Gründen, die mit der dynamischen Natur von JavaScript und der Verwendung von Prototypical-Inheritance zusammenhängen, ist es schwierig, klassenübergreifend konsistente Schnittstellen zu gewährleisten - es ist jedoch möglich, dies zu tun, und wird häufig emuliert.

An diesem Punkt gibt es eine Handvoll bestimmter Wege, um Schnittstellen in JavaScript zu emulieren; die Varianz der Ansätze befriedigt in der Regel einige Bedürfnisse, während andere unbehandelt bleiben. Oftmals ist der robusteste Ansatz übermäßig schwerfällig und behindert den Implementierer (Entwickler).

Hier ist ein Ansatz für Schnittstellen / Abstrakte Klassen, der nicht sehr schwerfällig ist, explizit ist, Implementierungen innerhalb von Abstraktionen auf ein Minimum beschränkt und genügend Raum für dynamische oder benutzerdefinierte Methoden lässt:

function resolvePrecept(interfaceName) {
    var interfaceName = interfaceName;
    return function curry(value) {
        /*      throw new Error(interfaceName + ' requires an implementation for ...');     */
        console.warn('%s requires an implementation for ...', interfaceName);
        return value;
    };
}

var iAbstractClass = function AbstractClass() {
    var defaultTo = resolvePrecept('iAbstractClass');

    this.datum1 = this.datum1 || defaultTo(new Number());
    this.datum2 = this.datum2 || defaultTo(new String());

    this.method1 = this.method1 || defaultTo(new Function('return new Boolean();'));
    this.method2 = this.method2 || defaultTo(new Function('return new Object();'));

};

var ConcreteImplementation = function ConcreteImplementation() {

    this.datum1 = 1;
    this.datum2 = 'str';

    this.method1 = function method1() {
        return true;
    };
    this.method2 = function method2() {
        return {};
    };

    //Applies Interface (Implement iAbstractClass Interface)
    iAbstractClass.apply(this);  // .call / .apply after precept definitions
};

Teilnehmer

Vorschrift Auflöser

En resolvePrecept Funktion ist ein Dienstprogramm und eine Hilfsfunktion zur Verwendung innerhalb Ihrer Abstrakte Klasse . Seine Aufgabe ist es, eine angepasste Handhabung der gekapselten Implementierung zu ermöglichen Grundsätze (Daten und Verhalten) . Er kann Fehler auslösen oder Warnungen ausgeben UND der Implementor-Klasse einen Standardwert zuweisen.

iAbstractClass

En iAbstractClass definiert die zu verwendende Schnittstelle. Ihr Ansatz beinhaltet eine stillschweigende Vereinbarung mit ihrer Implementor-Klasse. Diese Schnittstelle ordnet jedem Vorschrift in genau denselben Vorgabe-Namensraum -- ODER -- in denjenigen, den die Vorschrift Auflöser Funktion zurück. Die stillschweigende Übereinkunft führt jedoch zu einer Kontext -- eine Bestimmung des Implementors.

Durchführer

Der Implementor 'stimmt' einfach mit einer Schnittstelle überein ( iAbstractClass in diesem Fall) und wendet es durch die Verwendung von Konstrukteur-Hijacking : iAbstractClass.apply(this) . Indem Sie die Daten und das Verhalten oben definieren und dann Entführung des Konstruktors der Schnittstelle -- Übergabe des Implementor-Kontextes an den Konstruktor der Schnittstelle -- können wir sicherstellen, dass die Überschreibungen des Implementors hinzugefügt werden und dass die Schnittstelle Warnungen und Standardwerte angibt.

Dies ist ein sehr unkomplizierter Ansatz, der meinem Team und mir im Laufe der Zeit und bei verschiedenen Projekten sehr gute Dienste geleistet hat. Allerdings hat es einige Vorbehalte und Nachteile.

Beeinträchtigungen

Dies trägt zwar in erheblichem Maße dazu bei, die Konsistenz Ihrer Software zu gewährleisten, aber es keine echten Schnittstellen implementiert -- sondern ahmt sie nach. Obwohl Definitionen, Standardwerte und Warnungen oder Fehler son expliziert wird, ist die Explikation der Nutzung durchgesetzt & behauptet durch den Entwickler (wie bei einem Großteil der JavaScript-Entwicklung).

Dies scheint der beste Ansatz zu sein, um "Schnittstellen in JavaScript" Ich würde es jedoch begrüßen, wenn die folgenden Fragen gelöst würden:

  • Behauptungen von Rückgabetypen
  • Behauptungen von Unterschriften
  • Einfrieren von Objekten aus delete Aktionen
  • Behauptungen über alles andere, was in der JavaScript-Gemeinschaft verbreitet ist oder benötigt wird

In diesem Sinne hoffe ich, dass diese Informationen Ihnen genauso viel helfen wie meinem Team und mir.

12voto

Kevin Krausse Punkte 81

Abstrakte Schnittstelle wie diese

const MyInterface = {
  serialize: () => {throw "must implement serialize for MyInterface types"},
  print: () => console.log(this.serialize())
}

eine Instanz erstellen:

function MyType() {
  this.serialize = () => "serialized "
}
MyType.prototype = MyInterface

und verwenden Sie es

let x = new MyType()
x.print()

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X