20. Juli 2017 · von Andreas Klein

Interfaces mit JavaScript

Interfaces kennen wir von objektorientierten Sprachen wie C# oder Java. Sie sind ein Werkzeug um Abstraktion zu ermöglichen und dabei die problematische Mehrfachvererbung zu vermeiden.

In modernen Softwarearchitekturen ist Abstraktion ein wichtiges Element um z.B. Entwurfsmuster wie das Factory- oder Decorator-Pattern zu ermöglichen. JavaScript kennt Interfaces als Sprachelement nicht. Hier kann jedoch mit ein wenig Code nachgeholfen werden.

In einem unserer Projekte haben wir die Idee der Interfaces in unserem JavaScript Code verwendet und dabei folgendes Interface deklariert:

IField = function (fieldName, displayFieldName, fieldType, posX, posY, colspan, config) {
  this.fieldType = "";
  this.fieldName = "";
  this.displayFieldName = "";
  this.value = null;
  this.posX = 0;
  this.posY = 0;
  this.colspan = 0;

  if (fieldName) { this.fieldName = fieldName; }
  if (displayFieldName) { this.displayFieldName = displayFieldName; }
  if (fieldType) { this.fieldType = fieldType; }
  if (posX) { this.posX = posX; }
  if (posY) { this.posY = posY; }
  if (colspan) { this.colspan = colspan; }
};
/** 
 * Field Interface prototype
 */
FieldManagement.IField.prototype = {
  init: function () { return null; },
  resolveTemplate: function () { return ""; },
  serializeData: function () {
    var serializedData = {};
    serializeData[this.fieldName] = this.value;
    return serializedData;
  },
  deserializeData: function (data) {}
};

 

Wie man sehen kann ist das Interface nichts weiter als ein normales JavaScript Objekt. Um das Interface zu implementieren wird nun einfach in der implementierenden Objekt der Konstruktor aufgerufen:

TextField = function (fieldName, displayFieldName, posX, posY, colSpan, config) {
  IField.call(this, fieldName, displayFieldName, FieldTypeConstants.text, posX, posY, colSpan, config);
};

 

Diese Umsetzung eines Interfaces mit JavaScript birgt jedoch einige wesentliche Ungereimtheiten und Unschärfen:

  • Die Interface Deklaration enthält Attribute und Implementierungsdetails (z.B. serializeData) und
  • Das Interface erzwingt die Implementierung der deklarierten Funktionen nicht.

 

In Ihrem Buch Pro JavaScript Design Patterns schlagen Ross Harmes und Dustin Diaz eine etwas andere Implementierung vor. Diese basiert nicht auf der Vererbung wie der oben gezeigte Ansatz sondern darauf, dass ein Interface rein deklarativ ist und sichergestellt wird das ein Objekt alle Funktionen des Interfaces implementiert. Dazu wird sich einer Interface Klasse bedient die ein Interface deklariert. Diese erhält als Parameter den Namen des Interfaces und ein Array von Strings welche die Funktionen des Interfaces bezeichnen:

var IField = new Interface("IField",["serializeData","deserializeData"]);

 

Das Interface kann nun folgendermaßen implementiert werden:

var TextField = function(){
  Interface.ensureImplements(this,IField)
};
TextField.prototype.serializeData = function() { ... };
TextField.prototype.deserializeData = function() { ... };

 

Die Funktion Interface.ensureImplements überprüft nun ob das übergebene Objekt die Funktionen des Interfaces implementiert. Wenn dies nicht der Fall ist, wirft ensureImplements eine Exception.

Diese Variante eines JavaScript Interfaces behebt die oben genannten Probleme bereits. Das Interface selbst ist rein deklarativ und es erzwingt die Implementierung der Funktionen des Interfaces. Allerdings lässt sich diese Variante noch verbessern. Zum Einen kann bei der Deklaration des Interfaces einer Funktion keine Parameterliste mitgegeben und geprüft werden, zum Anderen ist eine API Dokumentation des Interfaces mittels JSDoc so ebenfalls nicht möglich.

Ausgehend von der Interface Umsetzung von Ross Harmes und Dustin Diaz haben wir die Deklaration eines Interfaces so abgeändert, dass diese kein Array von Strings erwartet, sondern ein Objekt mit Funktionsrümpfen. Diese Funktionsrümpfe werden nun genutzt um die Implementierung des Interfaces zu prüfen. Dabei überprüft ensureImplements auch ob die Parameteranzahl der Funktionen mit der im Interface angegeben Funktionsrumpf übereinstimmt. Ein weiterer Vorteil besteht darin, dass die Funktionsrümpfe des Interfaces mit JSDoc Kommentaren versehen werden können:

var IField = new Interface("IField",{
  /**
   * Serializes field data
   * @returns {object} serialized data
   */
  serializeData: function(){},
  /**
   * Deserializes field data
   * @param {object} data Serialized data
   * @Returns {object} deserialized object data
   */
  deserializeData: function(data){}
});

 

Die Implementierung des Interfaces erfolgt wie gehabt:

var TextField = function(){
  Interface.ensureImplements(this,IField)
};
TextField.prototype.serializeData = function() { ... };
TextField.prototype.deserializeData = function() { ... };

 

Der Code für die Interface Klasse kann hier heruntergeladen werden: JSFiddle


Kategorien: Erklärt, Technical

Schlagwörter: ,


Diesen Blogeintrag bewerten:


Haben Sie Fragen zu diesem Artikel oder brauchen Sie Unterstützung?

Nehmen Sie mit uns Kontakt auf!

Wir unterstützen Sie gerne bei Ihren Vorhaben!


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Kontakt
Lassen Sie sich von uns beraten
Wir freuen uns über Ihr Interesse an unseren Leistungen. Hinterlassen Sie
uns Ihren Namen, Ihre Telefonnummer und E-Mail Adresse – wir melden
uns schnellstmöglich bei Ihnen.
Kontakt aufnehmen