Datenkapselung in JavaScript: Getter und Setter
Bei der Entwicklung größerer JavaScript-Anwendungen ergibt sich schnell die Notwendigkeit, diese in Module aufzuteilen, die durch klare Verträge miteinander verbunden sind. Wenn es um langfristige Wartung geht, muss ein Weg gefunden werden, die Kompatibilität bei Schnittstellenänderungen zu erhalten. Um dies zu erreichen, stützen sich objektorientierte Sprachen auf die Kapselung oder auf Informationen, die Implementierungsdetails vor den Benutzern eines Codestücks verbergen, so dass es sich ändern kann, ohne dass sich dies auf die Clients auswirkt.
In diesem Beitrag werden wir über Datenkapselung in JavaScript sprechen. Genauer gesagt, werden wir über die Kapselung von Eigenschaften hinter Gettern und Settern sprechen.
Einige Konzepte
JavaScript wurde auf der Grundlage des Prinzips der Enten-Typisierung entwickelt:
“Wenn ich einen Vogel sehe, der wie eine Ente läuft, wie eine Ente schwimmt und wie eine Ente quakt, nenne ich diesen Vogel eine Ente.”
Mit anderen Worten, im Kern ist JavaScript eine objektorientierte Sprache, aber anders als bei anderen objektorientierten Sprachen wie C# und Java sind in JavaScript Klassen Bürger zweiter Klasse und Schnittstellen existieren nicht. Objekte haben keine Typen, so dass Code geschrieben werden muss, um Schnittstellen zu Objekten zu bilden, die bestimmte Eigenschaften und Methoden haben. Verträge zwischen Modulen basieren auf einer Reihe von Methoden und Eigenschaften, von denen angenommen wird, dass sie in den ausgetauschten Objekten vorhanden sind.
Einkapselung ist in der objektorientierten Programmierung grundlegend. Es bedeutet, dass ein bestimmtes Objekt in der Lage sein sollte, Details seines Innenlebens zu verbergen, so dass andere Objekte, die mit ihm interagieren, nicht von den Details seiner Implementierung abhängen, sondern von einer auf hohem Niveau vereinbarten Schnittstelle. Dies macht das Leben der Entwickler einfacher, sowohl auf der Seite des Anbieters als auch auf der Seite der Benutzer, da beide wissen, dass sich die Implementierungsdetails ändern können, ohne dass die Client-Objekte kaputt gehen.
Warum es eine schlechte Idee ist, Daten auf einer Schnittstelle zu veröffentlichen
In JavaScript ist ein Objekt einfach ein Wörterbuch von Eigenschaftsnamen, die auf Werte abgebildet werden, wenn der Wert einer Eigenschaft eine Funktion ist, nennen wir sie eine Methode. Die Schnittstelle zwischen den Methoden ist die Menge der Eigenschaften, die der Client-Code bei einem Objekt zu finden erwartet.
Wenn ein Objekt nur Methoden offenlegt, ist es einfach, die Schnittstelle mit der Zeit zu entwickeln. Eine Methode kann ihre Parameter überprüfen und entsprechend reagieren. Es ist auch möglich, neue Methoden zu erstellen, die neue Funktionen bieten. Und alte Methoden zu unterstützen, die das alte Verhalten an das neue anpassen. Bei Eigenschaften ist das nicht möglich.
Im Falle von Eigenschaften ist es nicht einfach, eine stabile Schnittstelle zu erhalten. Denn standardmäßig kann sich Client-Code auf nicht existierende Eigenschaften eines Objekts beziehen. Das Schreiben in eine nicht existierende Eigenschaft erzeugt diese. Das Lesen einer solchen Eigenschaft ist undefiniert. Der Client-Code wird nicht in der Lage sein, schnell zu erkennen, ob er veraltete Eigenschaften verwendet. Schlimmer noch, der Fehler kann sich auf andere Teile des Codes ausbreiten, was die Erkennung des Problems erschwert.
Die Standard-JavaScript-API hat Methoden, die bei der Vermeidung solcher Probleme nützlich sein können: Es ist möglich, ein Objekt einzufrieren oder zu versiegeln, so dass nicht unterstützte Eigenschaftsnamen nicht verwendet werden können.
Getter/Setter als Retter in der Not
Die Details der Methodenimplementierung lassen sich leicht verbergen.
Getter/Setter wurden mit ECMAScript 5.1 (ECMA-262) offiziell in die Sprache eingeführt. Sie werden derzeit von allen wichtigen Desktop- und mobilen Browsern unterstützt.
Die Grundidee ist, dass die Syntax hinzugefügt wird, um Accessor-Eigenschaften als Methoden zu definieren, anstelle von einfachen Dateneigenschaften. Ein Getter wird durch das Schlüsselwort get definiert, gefolgt von einer nach der Eigenschaft benannten Funktion, die keine Argumente benötigt und den Wert der Eigenschaft zurückgibt. Ein Setter wird durch das Schlüsselwort set definiert, gefolgt von einer nach der Eigenschaft benannten Funktion, die den neuen Wert der Eigenschaft als Parameter annimmt.
Das folgende Beispiel zeigt einen Getter und einen Setter, die verwendet werden, um eine Accessor-Eigenschaft namens prop zu definieren:
var obj = { v: 0, get prop() { return this.v; }, set prop(newValue) { this.v = newValue; }};console.log(obj.prop);obj.prop = 42;console.log(obj.prop);
Ausgabe:
042
Man kann auch eine schreibgeschützte Eigenschaft erstellen, indem man einen Getter ohne Setter definiert:
var obj = { get prop() { return -1; },};console.log(obj.prop);obj.prop = 42;console.log(obj.prop);
Output:
-1-1
Eine Eigenschaft kann dann durch eine Reihe von Methoden ersetzt werden, die, falls sich die Implementierung einer Klasse ändert, darauf reagieren und das Verhalten der Klasse anpassen können. Im schlimmsten Fall kann eine Ausnahme ausgelöst werden, um dem Benutzer mitzuteilen, dass die Eigenschaft veraltet ist und nicht mehr verwendet werden sollte.
Weiter lesen
Ich hoffe, Sie haben in diesem Beitrag etwas Neues über die Entwicklung komplexer evolutiver Anwendungen in JavaScript gelernt. In diesem Abschnitt gebe ich Ihnen ein paar Links zu weiterführenden Informationen.
Einführende Artikel
In diesem Abschnitt finden Sie ein paar kurze Artikel, die die Konzepte vorstellen und einen Überblick über die zugehörigen APIs geben:
- Property Getters und Setters.
- JavaScript Getters und Setters
Weitere Artikel für Fortgeschrittene
Diese Artikel befassen sich mit fortgeschritteneren Themen wie den Problemen, die durch Getters und Setters entstehen, und wie man sie lösen kann.
- Warum Getters/Setters in JavaScript eine schlechte Idee sind
- Datenkapselung in JavaScript
Referenzdokumentation
Um das Thema wirklich zu beherrschen, hier die Links zur Mozilla-Dokumentation der entsprechenden APIs:
- get und set erlauben die Definition von Getters und Setters. Über das hinaus, was wir hier erklärt haben. Es gibt andere interessante Funktionen wie die Unterstützung für dynamisch generierte Eigenschaftsnamen und die Verwendung von Gettern und Settern, um das Setzen/Gehen von Werten in einem Array zu simulieren
- seal und freeze erlauben es, zu kontrollieren, welche Eigenschaften eines Objekts veränderbar sind und wie. Dies kann verwendet werden, um zu vermeiden, dass Clients veraltete Teile der API eines Objekts verwenden.
- defineProperty erlaubt es, Getter und Setter zu definieren und gleichzeitig mehr Kontrolle darüber zu haben, wie diese Eigenschaften von Clients gesehen werden und wie veränderbar sie sind.