Heim  >  Artikel  >  Web-Frontend  >  Vertiefendes Verständnis der JavaScript-Reihe (17): Detaillierte Einführung in die objektorientierte Programmierung_Grundkenntnisse

Vertiefendes Verständnis der JavaScript-Reihe (17): Detaillierte Einführung in die objektorientierte Programmierung_Grundkenntnisse

WBOY
WBOYOriginal
2016-05-16 16:11:181081Durchsuche

Einführung

In diesem Artikel betrachten wir verschiedene Aspekte der objektorientierten Programmierung in ECMAScript (obwohl dieses Thema bereits in vielen Artikeln behandelt wurde). Wir werden diese Fragen eher aus einer theoretischen Perspektive betrachten. Insbesondere werden wir Algorithmen zur Objekterstellung betrachten, wie Objekte miteinander in Beziehung stehen (einschließlich grundlegender Beziehungen – Vererbung), die auch in Diskussionen verwendet werden können (was hoffentlich einige frühere konzeptionelle Unklarheiten über OOP in JavaScript beseitigen wird).

Englischer Originaltext:http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/

Einführung, Paradigma und Ideen

Bevor wir die technische Analyse von OOP in ECMAScript durchführen, müssen wir einige grundlegende Merkmale von OOP beherrschen und die Hauptkonzepte in der Einleitung klären.

ECMAScript unterstützt eine Vielzahl von Programmiermethoden, einschließlich strukturierter, objektorientierter, funktionaler, imperativer usw. In einigen Fällen unterstützt es auch aspektorientierte Programmierung, aber in diesem Artikel geht es um objektorientierte Programmierung. orientierte Programmierung in ECMAScript Definition:

ECMAScript ist eine objektorientierte Programmiersprache, die auf der Implementierung von Prototypen basiert.
Es gibt viele Unterschiede zwischen prototypbasierten OOP- und statischen klassenbasierten Ansätzen. Werfen wir einen Blick auf ihre Unterschiede im Detail.

Basierend auf Klassenattributen und basierend auf Prototypen

Beachten Sie, dass im vorherigen Satz auf einen wichtigen Punkt hingewiesen wurde – vollständig basierend auf statischen Klassen. Unter dem Wort „statisch“ verstehen wir statische Objekte und statische Klassen, die stark typisiert sind (obwohl nicht erforderlich).

In Bezug auf diese Situation haben viele Dokumente im Forum betont, dass dies der Hauptgrund ist, warum sie Einwände gegen den Vergleich von „Klassen mit Prototypen“ in JavaScript haben, obwohl ihre Implementierungen unterschiedlich sind (z. B. basierend auf dynamischen Klassen (Python und Ruby). Sie stehen nicht allzu sehr im Gegensatz zum Fokus (einige Bedingungen sind geschrieben, obwohl es bestimmte Denkunterschiede gibt, JavaScript ist nicht so alternativ geworden), aber der Fokus ihres Gegensatzes liegt auf statischen Klassen vs. dynamischen Prototypen), um genau zu sein, auf dem Mechanismus Durch die Betrachtung einer statischen Klasse (z. B. C, JAVA) und ihrer Untergeordneten und Methodendefinitionen können wir den genauen Unterschied zwischen ihr und einer prototypbasierten Implementierung erkennen.

Aber lasst uns sie einzeln auflisten. Betrachten wir allgemeine Prinzipien und die Hauptkonzepte dieser Paradigmen.

Basierend auf statischer Klasse

Im klassenbasierten Modell gibt es ein Konzept von Klassen und Instanzen. Instanzen von Klassen werden oft auch als Objekte oder Instanzen bezeichnet.

Klassen und Objekte

Eine Klasse repräsentiert eine Abstraktion einer Instanz (d. h. eines Objekts). In dieser Hinsicht ähnelt es ein wenig der Mathematik, aber wir nennen es einen Typ oder eine Klassifizierung.

Zum Beispiel (die Beispiele hier und unten sind Pseudocode):

Code kopieren Der Code lautet wie folgt:

C = Klasse {a, b, c} // Klasse C, einschließlich der Merkmale a, b, c

Die Merkmale einer Instanz sind: Eigenschaften (Objektbeschreibung) und Methoden (Objektaktivitäten). Auch Eigenschaften selbst können als Objekte betrachtet werden: Das heißt, ob die Eigenschaften beschreibbar, konfigurierbar, einstellbar (Getter/Setter) usw. sind. Somit speichern Objekte den Zustand (d. h. die spezifischen Werte aller in einer Klasse beschriebenen Eigenschaften), und Klassen definieren streng unveränderliche Strukturen (Eigenschaften) und streng unveränderliches Verhalten (Methoden) für ihre Instanzen.
Code kopieren Der Code lautet wie folgt:

C = Klasse {a, b, c, method1, method2}

c1 = {a: 10, b: 20, c: 30} // Klasse C ist eine Instanz: Objekt с1
c2 = {a: 50, b: 60, c: 70} // Klasse C ist eine Instanz: Objekt с2, das seinen eigenen Zustand (d. h. Attributwert) hat

Hierarchische Vererbung

Um die Wiederverwendung von Code zu verbessern, können Klassen von einer zur anderen erweitert und so zusätzliche Informationen hinzugefügt werden. Dieser Mechanismus wird als (hierarchische) Vererbung bezeichnet.

Code kopieren Der Code lautet wie folgt:

D = Klasse erweitert C = {d, e} // {a, b, c, d, e}
d1 = {a: 10, b: 20, c: 30, d: 40, e: 50}

Wenn Sie eine Methode für eine Instanz einer Klasse aufrufen, suchen Sie normalerweise nach der Methode in der nativen Klasse. Wenn sie nicht gefunden wird, gehen Sie zur Suche zur direkten übergeordneten Klasse Die zu durchsuchende übergeordnete Klasse (z. B. in einer strikten Vererbungskette) lautet: Wenn die Spitze der Vererbung gefunden, aber noch nicht gefunden wurde, lautet das Ergebnis: Das Objekt weist kein ähnliches Verhalten auf und es gibt keine Möglichkeit, das Ergebnis zu erhalten.

Code kopieren Der Code lautet wie folgt:

d1.method1() // D.method1 (nein) -> C.method1 (ja)
d1.method5() // D.method5 (nein) -> C.method5 (nein) ->
Im Gegensatz zur Vererbung, bei der Methoden nicht in eine Unterklasse kopiert werden, werden Eigenschaften immer in Unterklassen kopiert. Wir können sehen, dass die Unterklasse D von der übergeordneten Klasse C erbt: Die Attribute a, b, c werden kopiert und die Struktur von D ist {a, b, c, d, e}}. Allerdings werden die Methoden {method1, method2} nicht aus der Vergangenheit kopiert, sondern aus der Vergangenheit geerbt. Wenn also eine tiefe Klasse einige Attribute hat, die das Objekt überhaupt nicht benötigt, dann hat die Unterklasse auch diese Attribute.

Schlüsselkonzepte basierend auf Klassen

Daher haben wir die folgenden Schlüsselkonzepte:

1. Vor dem Erstellen eines Objekts muss die Klasse zunächst deklariert werden

2. Daher wird das Objekt aus der Klasse erstellt, die in sein eigenes „Ikonogramm und seine Ähnlichkeit“ (Struktur und Verhalten) abstrahiert wird
3. Methoden werden über eine strenge, direkte und unveränderliche Vererbungskette verarbeitet
4. Die Unterklasse enthält alle Attribute in der Vererbungskette (auch wenn einige der Attribute von der Unterklasse nicht benötigt werden); 5. Eine Klasseninstanz erstellen (aufgrund des statischen Modells) kann die Eigenschaften (Eigenschaften oder Methoden) ihrer Instanz nicht ändern 6. Instanzen können (aufgrund des strengen statischen Modells) keine zusätzlichen Verhaltensweisen oder Attribute haben, die nicht in der der Instanz entsprechenden Klasse deklariert sind.

Sehen wir uns an, wie das OOP-Modell in JavaScript ersetzt werden kann, was wir basierend auf dem OOP-Prototyp vorschlagen.

Basierend auf einem Prototyp

Das Grundkonzept hier sind dynamische veränderbare Objekte. Transformationen (vollständige Transformationen, die nicht nur Werte, sondern auch Attribute umfassen) stehen in direktem Zusammenhang mit dynamischen Sprachen. Objekte wie die folgenden können alle ihre Eigenschaften (Eigenschaften, Methoden) unabhängig speichern, ohne dass eine Klasse erforderlich ist.



Code kopieren Der Code lautet wie folgt: Objekt = {a: 10, b: 20, c: 30, Methode: fn};
object.a; // 10
object.c; // 30
object.method();



Da sie außerdem dynamisch sind, können sie ihre Eigenschaften leicht ändern (hinzufügen, löschen, ändern):


Code kopieren Der Code lautet wie folgt: object.method5 = function () {...}; // Neue Methode hinzufügen
object.d = 40; // Neues Attribut „d“ hinzufügen
delete object.c; // Attribut „с“ löschen
object.a = 100; // Attribut „а“ ändern

// Das Ergebnis ist: object: {a: 100, b: 20, d: 40, method: fn, method5: fn};



Das heißt, wenn zum Zeitpunkt der Zuweisung ein Attribut nicht vorhanden ist, erstellen Sie es und initialisieren Sie die Zuweisung damit. Wenn es vorhanden ist, aktualisieren Sie es einfach.
In diesem Fall wird die Code-Wiederverwendung nicht durch Erweitern der Klasse erreicht (bitte beachten Sie, dass wir nicht gesagt haben, dass die Klasse nicht geändert werden kann, da es hier kein Klassenkonzept gibt), sondern durch einen Prototyp.

Ein Prototyp ist ein Objekt, das als primitive Kopie anderer Objekte verwendet wird. Wenn einige Objekte nicht über die erforderlichen eigenen Eigenschaften verfügen, kann der Prototyp als Delegat für diese Objekte verwendet werden und als Hilfsobjekt dienen .

Delegiert basierend

Jedes Objekt kann als Prototypobjekt für ein anderes Objekt verwendet werden, da ein Objekt seinen Prototyp zur Laufzeit leicht dynamisch ändern kann.

Beachten Sie, dass wir derzeit eher einen Überblick als eine spezifische Implementierung betrachten. Wenn wir spezifische Implementierungen in ECMAScript diskutieren, werden wir einige ihrer eigenen Merkmale sehen.

Beispiel (Pseudocode):


Code kopieren Der Code lautet wie folgt: x = {a: 10, b: 20};
y = {a: 40, c: 50};
y.[[Prototyp]] = x; // x ist der Prototyp von y

y.a; // 40, eigene Eigenschaften
y.c; // 50, auch seine eigenen Eigenschaften
y.b; // 20 – erhalten vom Prototyp: y.b (nein) -> y.[[Prototyp]].b (ja): 20

delete y.a; // Eigenes „а“ löschen
y.a; // 10 – Holen Sie sich
vom Prototyp
z = {a: 100, e: 50}
y.[[Prototype]] = z; // Ändere den Prototyp von y in z
y.a; // 100 – Holen Sie sich
vom Prototyp z y.e // 50, ebenfalls erhalten vom Prototyp z

z.q = 200 // Neue Eigenschaften zum Prototyp hinzufügen
y.q // Änderung gilt auch für y

Dieses Beispiel zeigt die wichtige Funktion und den Mechanismus des Prototyps als Hilfsobjektattribut, genau wie die Abfrage seiner eigenen Attribute. Im Vergleich zu seinen eigenen Attributen sind diese Attribute Delegatenattribute. Dieser Mechanismus wird als Delegate bezeichnet, und ein darauf basierendes Prototypmodell ist ein Delegate-Prototyp (oder delegiertenbasierter Prototyp). Der Referenzmechanismus wird hier als Senden einer Nachricht an ein Objekt bezeichnet. Wenn das Objekt keine Antwort erhält, wird es an den Prototyp delegiert, es zu finden (was ihn dazu zwingt, zu versuchen, auf die Nachricht zu antworten).

Die Wiederverwendung von Code wird in diesem Fall als delegiertenbasierte Vererbung oder prototypbasierte Vererbung bezeichnet. Da jedes Objekt als Prototyp verwendet werden kann, bedeutet dies, dass ein Prototyp auch einen eigenen Prototyp haben kann. Diese Prototypen werden zu einer sogenannten Prototypenkette miteinander verknüpft. Ketten sind ebenfalls hierarchisch wie statische Klassen, können jedoch leicht neu angeordnet werden, um die Hierarchie und Struktur zu ändern.

Code kopieren Der Code lautet wie folgt:

x = {a: 10}

y = {b: 20}
y.[[Prototyp]] = x

z = {c: 30}
z.[[Prototyp]] = y

z.a // 10

// z.a befindet sich in der Prototypenkette:
// z.a (nein) ->
// z.[[Prototyp]].a (nein) ->
// z.[[Prototyp]].[[Prototyp]].a (ja): 10

Wenn ein Objekt und seine Prototypenkette nicht auf das Senden von Nachrichten reagieren können, kann das Objekt das entsprechende Systemsignal aktivieren, das möglicherweise von anderen Delegierten in der Prototypenkette verarbeitet wird.

Dieses Systemsignal ist in vielen Implementierungen verfügbar, einschließlich Systemen, die auf dynamischen Klassen in Klammern basieren: #doesNotUnderstand in Smalltalk, method_missing in Python, __call in PHP und __noSuchMethod__-Implementierung in ECMAScript usw.

Beispiel (ECMAScript-Implementierung von SpiderMonkey):

Code kopieren Der Code lautet wie folgt:

var object = {

//Systemsignale abfangen, die nicht auf Nachrichten reagieren können
__noSuchMethod__: Funktion (Name, Argumente) {
alarm([name, args]);
If (name == 'test') {
         return '.test() method is handled';
}
Geben Sie „delegate[name].apply(this, args);
“ zurück }

};

var Delegate = {
Quadrat: Funktion (a) {
Geben Sie a * a;
zurück }
};

alarm(object.square(10)); // 100
Alert(object.test()); // .test() Methode wird behandelt

Mit anderen Worten: Wenn die auf der statischen Klasse basierende Implementierung nicht auf die Nachricht reagieren kann, besteht die Schlussfolgerung darin, dass das aktuelle Objekt nicht über die erforderlichen Eigenschaften verfügt. Wenn Sie jedoch versuchen, es aus der Prototypenkette abzurufen, kann dies dennoch der Fall sein das Ergebnis erhalten, oder das Objekt besitzt diese Eigenschaft nach einer Reihe von Änderungen.

In Bezug auf ECMAScript lautet die spezifische Implementierung: Verwendung von delegiertenbasierten Prototypen. Wie wir jedoch anhand der Spezifikation und Implementierung sehen werden, haben sie auch ihre eigenen Eigenschaften.

Konkatenatives Modell

Ehrlich gesagt ist es notwendig, etwas über eine andere Situation zu sagen (sobald sie nicht in ECMASCript verwendet wird): die Situation, in der der Prototyp das native Objekt durch andere Objekte ersetzt. Bei der Wiederverwendung von Code handelt es sich in diesem Fall um eine echte Kopie (Klon) eines Objekts während der Objekterstellungsphase und nicht um eine Delegation. Diese Art von Prototyp wird als verketteter Prototyp bezeichnet. Das Kopieren aller Prototypeigenschaften eines Objekts kann seine Eigenschaften und Methoden weiter vollständig ändern, und der Prototyp kann sich auch selbst ändern (in einem delegiertenbasierten Modell ändert diese Änderung nicht das vorhandene Objektverhalten, sondern seine Prototypeigenschaften). Der Vorteil dieser Methode besteht darin, dass die Planungs- und Delegationszeit verkürzt werden kann. Der Nachteil besteht jedoch darin, dass die Speichernutzung hoch ist.

Ententyp

Im Vergleich zu Modellen, die auf statischen Klassen basieren, hat das Zurückgeben von Objekten, die diese Dinge tun können, nichts mit dem Typ (der Klasse) des Objekts zu tun, sondern ob es auf die Nachricht (das) reagieren kann ist, nachdem geprüft wurde, ob die Fähigkeit dazu ein Muss ist).

Zum Beispiel:

Code kopieren Der Code lautet wie folgt:

// In einem statischen Modell
if (Objektinstanz von SomeClass) {
// Einige Aktionen werden ausgeführt
}

// In dynamischer Implementierung
// An dieser Stelle spielt es keine Rolle, um welchen Typ es sich bei dem Objekt handelt
// Weil Mutationen, Typen und Eigenschaften frei und wiederholt transformiert werden können.
// Ob wichtige Objekte auf Testnachrichten reagieren können
if (isFunction(object.test)) // ECMAScript

if object.respond_to?(:test) // Ruby

if hasattr(object, 'test'): // Python

Dies ist der sogenannte Dock-Typ. Das heißt, Objekte können bei der Prüfung anhand ihrer eigenen Merkmale identifiziert werden und nicht anhand der Position des Objekts in der Hierarchie oder seiner Zugehörigkeit zu einem bestimmten Typ.

Schlüsselkonzepte basierend auf Prototypen

Werfen wir einen Blick auf die Hauptmerkmale dieses Ansatzes:

1. Das Grundkonzept ist Objekt
2. Das Objekt ist vollständig dynamisch und variabel (theoretisch kann es von einem Typ in einen anderen umgewandelt werden)
3. Objekte haben keine strengen Klassen, die ihre eigene Struktur und ihr eigenes Verhalten beschreiben. Objekte benötigen keine Klassen
4. Objekte haben keine Klassen, können aber Prototypen haben. Wenn sie nicht auf Nachrichten antworten können, können sie an den Prototyp
delegiert werden 5. Der Prototyp des Objekts kann jederzeit zur Laufzeit geändert werden;
6. Im delegiertenbasierten Modell wirkt sich die Änderung der Eigenschaften des Prototyps auf alle mit dem Prototyp verbundenen Objekte aus;
7. Im verketteten Prototypenmodell ist der Prototyp eine von anderen Objekten geklonte Originalkopie und wird darüber hinaus zu einer völlig unabhängigen Kopie des Originals. Die Transformation der Prototypeigenschaften hat keine Auswirkungen auf die daraus geklonten Objekte
8. Wenn die Nachricht nicht beantwortet werden kann, kann der Anrufer zusätzliche Maßnahmen ergreifen (z. B. die Planung ändern)
9. Der Ausfall von Objekten kann nicht anhand ihres Levels und ihrer Klassenzugehörigkeit bestimmt werden, sondern anhand der aktuellen Eigenschaften

Es gibt jedoch noch ein anderes Modell, das wir ebenfalls berücksichtigen sollten.

Basierend auf dynamischen Klassen

Wir glauben, dass die im obigen Beispiel gezeigte Unterscheidung „Klasse vs. Prototyp“ in diesem auf dynamischen Klassen basierenden Modell nicht so wichtig ist (insbesondere wenn die Prototypenkette unveränderlich ist, ist dies für eine genauere Unterscheidung dennoch erforderlich Betrachten Sie eine statische Klasse). Beispielsweise könnten auch Python oder Ruby (oder andere ähnliche Sprachen) verwendet werden. Diese Sprachen verwenden alle ein dynamisches klassenbasiertes Paradigma. In einigen Aspekten können wir jedoch sehen, dass einige auf dem Prototyp basierende Funktionen implementiert wurden.

Im folgenden Beispiel können wir sehen, dass wir eine Klasse (Prototyp) erweitern können, wodurch alle mit dieser Klasse verbundenen Objekte beeinflusst werden. Wir können dieses Objekt auch zur Laufzeit dynamisch ändern Objekt für den Delegierten) und so weiter.

Code kopieren Der Code lautet wie folgt:

#Python

Klasse A(Objekt):

Def __init__(self, a):
         self.a = a

Def quadrat(selbst):
          return self.a * self.a

a = A(10) # Instanz erstellen
print(a.a) # 10

A.b = 20 # Geben Sie ein neues Attribut für die Klasse
an print(a.b) # 20 –
kann in der „a“-Instanz aufgerufen werden
a.b = 30 # Eigene Attribute von a erstellen
print(a.b) # 30

del a.b # Eigene Attribute löschen
print(a.b) # 20 – Holen Sie sich (Prototyp) erneut aus der Klasse

# Genau wie ein prototypbasiertes Modell
# Sie können den Prototyp des Objekts zur Laufzeit ändern

Klasse B(Objekt): # Leere Klasse B
Pass

b = B() # Instanz von B

b.__class__ = A # Klasse (Prototyp) dynamisch ändern

b.a = 10 # Neues Attribut erstellen
print(b.square()) # 100 – Methoden der Klasse A sind derzeit verfügbar

# Sie können Verweise auf gelöschte Klassen anzeigen
del A
del B

# Das Objekt verfügt jedoch weiterhin über implizite Referenzen und diese Methoden sind weiterhin verfügbar
print(b.square()) # 100

# Aber die Klasse kann derzeit nicht geändert werden
# Dies ist eine implementierte Funktion
b.__class__ = dict # Fehler

Die Implementierung in Ruby ist ähnlich: Es werden auch volldynamische Klassen verwendet (in der aktuellen Version von Python funktioniert das Vergrößern von Klassen (Prototypen) übrigens im Gegensatz zu Ruby und ECMAScript nicht), wir können das Objekt komplett ändern (oder Klassen-)Eigenschaften (Hinzufügen von Methoden/Eigenschaften zur Klasse, und diese Änderungen wirken sich auf vorhandene Objekte aus), die Klasse eines Objekts kann jedoch nicht dynamisch geändert werden.

In diesem Artikel geht es jedoch nicht speziell um Python und Ruby, daher werden wir nicht mehr sagen und uns weiter mit ECMAScript selbst befassen.

Aber vorher müssen wir uns noch einmal mit dem „syntaktischen Zucker“ befassen, der in manchen OOPs vorkommt, da viele frühere Artikel über JavaScript diese Probleme häufig behandeln.

Der einzige falsche Satz, den es in diesem Abschnitt zu beachten gilt, ist: „JavaScript ist keine Klasse, es hat Prototypen, die Klassen ersetzen können.“ Es ist wichtig zu wissen, dass nicht alle klassenbasierten Implementierungen völlig unterschiedlich sind. Auch wenn wir vielleicht sagen: „JavaScript ist anders“, muss man auch berücksichtigen, dass es (neben dem Konzept der „Klassen“) noch andere verwandte Merkmale gibt .

Weitere Funktionen verschiedener OOP-Implementierungen

In diesem Abschnitt stellen wir kurz andere Funktionen und Methoden der Code-Wiederverwendung in verschiedenen OOP-Implementierungen vor, einschließlich OOP-Implementierungen in ECMAScript. Der Grund dafür ist, dass es bei der Implementierung von OOP in JavaScript einige gewohnheitsmäßige Einschränkungen gibt. Die einzige Hauptanforderung besteht darin, dass es technisch und ideologisch nachgewiesen werden muss. Es kann nicht gesagt werden, dass wir die syntaktische Zuckerfunktion nicht in anderen OOP-Implementierungen entdeckt haben, und wir haben voreilig angenommen, dass JavaScript keine reine OOP-Sprache ist. Das ist falsch.

Polymorph

Objekte haben in ECMAScript mehrere Bedeutungen von Polymorphismus.

Zum Beispiel kann eine Funktion auf verschiedene Objekte angewendet werden, genau wie die Eigenschaften des nativen Objekts (da der Wert beim Eintritt in den Ausführungskontext bestimmt wird):

Code kopieren Der Code lautet wie folgt:

Funktionstest() {
alarm([this.a, this.b]);
}

test.call({a: 10, b: 20}); // 10, 20
test.call({a: 100, b: 200}); // 100, 200

var a = 1;
var b = 2;

test(); // 1, 2

Es gibt jedoch Ausnahmen: die Methode Date.prototype.getTime(), die laut Standard immer ein Datumsobjekt haben sollte, sonst wird eine Ausnahme geworfen.
Code kopieren Der Code lautet wie folgt:

Alert(Date.prototype.getTime.call(new Date())); // time
Alert(Date.prototype.getTime.call(new String(''))); // TypeError

Der sogenannte Parameterpolymorphismus beim Definieren einer Funktion entspricht allen Datentypen, außer dass er polymorphe Parameter akzeptiert (z. B. die Sortiermethode .sort des Arrays und seiner Parameter – polymorphe Sortierfunktion). Das obige Beispiel kann übrigens auch als eine Art parametrischer Polymorphismus betrachtet werden.

Methoden im Prototyp können als leer definiert werden, und alle erstellten Objekte sollten diese Methode neu definieren (implementieren) (d. h. „eine Schnittstelle (Signatur), mehrere Implementierungen“).

Polymorphismus hängt mit dem oben erwähnten Duck-Typ zusammen: d. h. der Typ und die Position des Objekts in der Hierarchie sind nicht so wichtig, aber wenn es alle erforderlichen Eigenschaften aufweist, kann es leicht akzeptiert werden (d. h. gemeinsame Schnittstellen sind wichtig). , Implementierungen können vielfältig sein).

Kapselung

Es gibt oft Missverständnisse über die Kapselung. In diesem Abschnitt besprechen wir einige syntaktische Zucker in OOP-Implementierungen – auch Modifikatoren genannt: In diesem Fall besprechen wir einige praktische „Zucker“ in OOP-Implementierungen – bekannte Modifikatoren: privat, geschützt und öffentlich (auch als Objektzugriff bekannt). Ebene oder Zugriffsmodifikator).

Hier möchte ich Sie an den Hauptzweck der Kapselung erinnern: Die Kapselung ist eine abstrakte Ergänzung, kein versteckter „böswilliger Hacker“, der etwas direkt in Ihre Klasse schreibt.

Das ist ein großer Fehler: Verwenden Sie hide, um sich zu verstecken.

Zugriffsebenen (privat, geschützt und öffentlich) wurden in vielen objektorientierten Programmen implementiert, um die Programmierung (wirklich sehr praktischer Syntaxzucker) zu erleichtern und Systeme abstrakter zu beschreiben und aufzubauen.

Dies ist in einigen Implementierungen zu sehen (z. B. Python und Ruby, die bereits erwähnt wurden). Einerseits (in Python) sind diese __private_protected-Attribute (benannt über die Unterstrichkonvention) von außen nicht zugänglich. Auf Python hingegen kann mit speziellen Regeln (_ClassName__field_name) von außen zugegriffen werden.

Code kopieren Der Code lautet wie folgt:

Klasse A(Objekt):

Def __init__(self):
        self.public = 10
        self.__private = 20

Def get_private(self):
          return self.__private

# draußen:

a = A() # Instanz von A

print(a.public) # OK, 30
print(a.get_private()) # OK, 20
print(a.__private) # Fehlgeschlagen, da es nur in A
verwendet werden kann
# In Python kann jedoch über spezielle Regeln darauf zugegriffen werden

print(a._A__private) # OK, 20

In Ruby: Einerseits besteht die Möglichkeit, private und geschützte Eigenschaften zu definieren. Andererseits gibt es auch spezielle Methoden (z. B. Instanz_Variable_Get, Instanz_Variable_Set, Senden usw.), um gekapselte Daten zu erhalten.

Code kopieren Der Code lautet wie folgt:

Klasse A

def initialisieren
@a = 10
Ende

def public_method
private_method(20)
Ende

privat

def private_method(b)
Geben Sie @a b
zurück Ende

Ende

a = A.new # Neue Instanz

a.public_method # OK, 30

a.a # Fehler, @a – ist eine private Instanzvariable

# „private_method“ ist privat und kann nur in Klasse A
aufgerufen werden
a.private_method # Fehler

# Es gibt jedoch einen speziellen Metadatenmethodennamen, der Daten abrufen kann

a.send(:private_method, 20) # OK, 30
a.instance_variable_get(:@a) # OK, 10

Der Hauptgrund ist, dass der Programmierer selbst die gekapselten (beachten Sie, dass ich ausdrücklich keine „versteckten“ Daten verwende) erhalten möchte. Wenn sich diese Daten in irgendeiner Weise falsch ändern oder Fehler aufweisen, liegt die volle Verantwortung beim Programmierer, nicht aber nur bei „Tippfehlern“ oder „nur bei der Änderung einiger Felder“. Wenn dies jedoch häufig vorkommt, handelt es sich um eine sehr schlechte Programmiergewohnheit und einen sehr schlechten Programmierstil, da es sich normalerweise lohnt, die öffentliche API zu verwenden, um mit dem Objekt zu „sprechen“.

Um es noch einmal zu wiederholen: Der Hauptzweck der Kapselung besteht darin, den Benutzer von Hilfsdaten zu abstrahieren und nicht, Hacker daran zu hindern, die Daten zu verbergen. Noch schlimmer ist, dass bei der Kapselung keine privaten Daten zum Ändern von Daten verwendet werden, um Softwaresicherheit zu erreichen.

Kapseln Sie Hilfsobjekte (teilweise). Wir nutzen minimale Kosten, Lokalisierung und prädiktive Änderungen, um Verhaltensänderungen in öffentlichen Schnittstellen möglich zu machen.

Darüber hinaus besteht der wichtige Zweck der Setter-Methode darin, komplexe Berechnungen zu abstrahieren. Beispielsweise ist der element.innerHTML-Setter – die abstrakte Anweisung – „Der HTML-Code in diesem Element ist jetzt der folgende Inhalt“ und die Setter-Funktion in der innerHTML-Eigenschaft schwer zu berechnen und zu überprüfen. In diesem Fall liegt das Problem hauptsächlich in der Abstraktion, aber es kommt auch zu einer Kapselung.

Das Konzept der Kapselung bezieht sich nicht nur auf OOP. Beispielsweise kann es sich um eine einfache Funktion handeln, die lediglich verschiedene Berechnungen kapselt und dadurch abstrakt macht (der Benutzer muss beispielsweise nicht wissen, wie die Funktion Math.round(...) implementiert ist, der Benutzer ruft sie einfach auf Es). Es handelt sich um eine Art Kapselung. Ich habe nicht gesagt, dass es „privat, geschützt und öffentlich“ ist.

Die aktuelle Version der ECMAScript-Spezifikation definiert nicht die privaten, geschützten und öffentlichen Modifikatoren.

In der Praxis ist es jedoch möglich, etwas namens „Mock JS Encapsulation“ zu sehen. Im Allgemeinen soll dieser Kontext verwendet werden (in der Regel der Konstruktor selbst). Leider wird diese „Mimikry“ oft implementiert und Programmierer können pseudo-absolut nicht-abstrakte „Getter/Setter-Methoden“ für Entitätseinstellungen erzeugen (ich wiederhole, es ist falsch):

Code kopieren Der Code lautet wie folgt:

Funktion A() {

var _a; // "privat" a

this.getA = Funktion _getA() {
Geben Sie _a;
zurück };

this.setA = Funktion _setA(a) {
_a = a;
};

}

var a = new A();

a.setA(10);
Alert(a._a); // undefiniert, „privat“
alarm(a.getA()); // 10

Jeder versteht also, dass für jedes erstellte Objekt auch die getA/setA-Methoden erstellt werden, was auch der Grund für die Speichererweiterung (im Vergleich zur Prototypdefinition) ist. Obwohl theoretisch das Objekt im ersten Fall optimiert werden kann.

Darüber hinaus wird in einigen JavaScript-Artikeln häufig das Konzept der „privaten Methoden“ erwähnt. Hinweis: Der ECMA-262-3-Standard definiert kein Konzept der „privaten Methoden“.

In einigen Fällen kann es jedoch im Konstruktor erstellt werden, da JS eine ideologische Sprache ist – Objekte sind vollständig veränderbar und haben einzigartige Eigenschaften (unter bestimmten Bedingungen im Konstruktor können einige Objekte zusätzliche Methoden erhalten, andere nicht). ).

Wenn Kapselung in JavaScript außerdem immer noch als ein Verständnis missverstanden wird, das böswillige Hacker daran hindert, bestimmte Werte automatisch zu schreiben, anstatt die Setter-Methode zu verwenden, dann sind die sogenannten „versteckten“ und „privaten“ ist eigentlich nicht sehr „versteckt“, einige Implementierungen können den Wert in der relevanten Bereichskette (und den entsprechenden allen variablen Objekten) abrufen, indem sie den Kontext der Eval-Funktion aufrufen (kann auf SpiderMonkey1.7 getestet werden).

Code kopieren Der Code lautet wie folgt:

eval('_a = 100', a.getA); // oder a.setA, weil die beiden Methoden „_a“ auf [[Scope]]
liegen a.getA(); // 100

Alternativ ermöglicht die Implementierung den direkten Zugriff auf das aktive Objekt (z. B. Rhino) und der Wert der internen Variablen kann durch Zugriff auf die entsprechende Eigenschaft des Objekts geändert werden:

Code kopieren Der Code lautet wie folgt:

// Nashorn
var foo = (function () {
var x = 10; // "privat"
Rückgabefunktion () {
Drucken(x);
};
})();
foo(); // 10
foo.__parent__.x = 20;
foo(); // 20

Manchmal werden in JavaScript „private“ und „geschützte“ Daten dadurch erreicht, dass Variablen ein Unterstrich vorangestellt wird (aber im Vergleich zu Python ist dies nur eine Namenskonvention):

var _myPrivateData = 'testString';
Es wird oft verwendet, um den Ausführungskontext in Klammern zu setzen, aber für echte Hilfsdaten steht es nicht in direktem Zusammenhang mit dem Objekt, sondern eignet sich lediglich zum Abstrahieren von der externen API:

Code kopieren Der Code lautet wie folgt:

(Funktion () {

// Kontext initialisieren

})();

Mehrfachvererbung

Mehrfachvererbung ist ein sehr praktischer syntaktischer Zucker, um die Wiederverwendung von Code zu verbessern (wenn wir jeweils eine Klasse erben können, warum können wir dann nicht 10 gleichzeitig erben?). Aufgrund einiger Mängel der Mehrfachvererbung hat sie sich jedoch bei der Implementierung nicht durchgesetzt.

ECMAScript unterstützt keine Mehrfachvererbung (d. h. nur ein Objekt kann als direkter Prototyp verwendet werden), obwohl seine Vorgänger-Programmiersprache über eine solche Fähigkeit verfügt. Aber in einigen Implementierungen (z. B. SpiderMonkey) kann die Verwendung von __noSuchMethod__ anstelle der Prototypenkette zur Verwaltung von Planung und Delegation verwendet werden.

Mixins

Mixins sind eine praktische Möglichkeit, Code wiederzuverwenden. Mixins wurden als Alternativen zur Mehrfachvererbung vorgeschlagen. Jedes dieser einzelnen Elemente kann mit jedem Objekt gemischt werden, um deren Funktionalität zu erweitern (so können Objekte auch mit mehreren Mixins gemischt werden). Die ECMA-262-3-Spezifikation definiert nicht das Konzept von „Mixins“, aber gemäß der Definition von Mixins und ECMAScript über dynamisch veränderbare Objekte gibt es kein Hindernis dafür, Funktionen einfach mithilfe von Mixins zu erweitern.

Typisches Beispiel:

Code kopieren Der Code lautet wie folgt:

// Helfer für die Erweiterung
Object.extend = Funktion (Ziel, Quelle) {
for (Eigenschaft in Quelle) if (source.hasOwnProperty(property)) {
Ziel[Eigenschaft] = Quelle[Eigenschaft];
}
Rückflugziel;
};

var X = {a: 10, b: 20};
var Y = {c: 30, d: 40};

Object.extend(X, Y); // Y in X mischen
alarm([X.a, X.b, X.c, X.d]); 10, 20, 30, 40

Bitte beachten Sie, dass ich diese Definitionen („mixin“, „mix“) in Anführungszeichen verwende, die in ECMA-262-3 erwähnt werden. Es gibt kein solches Konzept in der Spezifikation und es ist nicht „mix“, sondern wird häufig zum Erweitern von Objekten verwendet mit neuen Funktionen. (Das Konzept von Mixins in Ruby ist offiziell definiert. Mixins erstellen einen Verweis auf ein enthaltendes Modul, anstatt einfach alle Eigenschaften des Moduls in ein anderes Modul zu kopieren – tatsächlich: Erstellen eines zusätzlichen Objekts (Prototyps) für den Delegaten. ).

Eigenschaften

Merkmale ähneln im Konzept Mixins, verfügen jedoch über viele Funktionen (da Mixins per Definition angewendet werden können, können sie keinen Status enthalten, da dies zu Namenskonflikten führen kann). Laut ECMAScript folgen Traits und Mixins denselben Prinzipien, sodass die Spezifikation das Konzept von „Traits“ nicht definiert.

Schnittstelle

Die in einigen OOP implementierten Schnittstellen ähneln Mixins und Traits. Im Gegensatz zu Mixins und Traits zwingen Schnittstellen implementierende Klassen jedoch dazu, das Verhalten ihrer Methodensignaturen zu implementieren.

Schnittstellen können vollständig als abstrakte Klassen betrachtet werden. Im Vergleich zu abstrakten Klassen (Methoden in abstrakten Klassen können nur einen Teil der Methode implementieren und der andere Teil bleibt als Signatur definiert) kann die Vererbung jedoch nur eine einzelne Basisklasse, aber mehrere Schnittstellen erben. Schnittstellen (multiple Mixed) können als Alternative zur Mehrfachvererbung angesehen werden.

Der ECMA-262-3-Standard definiert weder das Konzept der „Schnittstelle“ noch das Konzept der „abstrakten Klasse“. Als Nachahmung ist es jedoch möglich, ein Objekt mit einer „leeren“ Methode zu implementieren (oder eine Ausnahme, die in eine leere Methode geworfen wird, um dem Entwickler mitzuteilen, dass diese Methode implementiert werden muss).

Objektkombination

Objektzusammensetzung ist auch eine der dynamischen Code-Wiederverwendungstechnologien. Die Objektkomposition unterscheidet sich von der hochflexiblen Vererbung dadurch, dass sie einen dynamisch veränderbaren Delegaten implementiert. Und dies auch auf Basis in Auftrag gegebener Prototypen. Zusätzlich zu dynamisch veränderbaren Prototypen kann das Objekt Objekte für Delegaten aggregieren (als Ergebnis eine Kombination erstellen – eine Aggregation) und außerdem Nachrichten an Objekte senden, die an den Delegaten delegieren. Dies kann mit mehr als zwei Delegaten durchgeführt werden, da es sich aufgrund seiner dynamischen Natur zur Laufzeit ändern kann.

Das bereits erwähnte __noSuchMethod__-Beispiel tut dies, aber wir zeigen auch, wie man Delegaten explizit verwendet:

Zum Beispiel:

Code kopieren Der Code lautet wie folgt:

var _delegate = {
foo: Funktion () {
alarm('_delegate.foo');
}
};

varaggregat = {

Delegat: _delegate,

foo: Funktion () {
Geben Sie this.delegate.foo.call(this);
zurück }

};

aggregat.foo(); // Delegate.foo

aggregat.delegate = {
foo: Funktion () {
Alert('foo vom neuen Delegierten');
}
};

aggregat.foo(); // foo vom neuen Delegaten

Diese Objektbeziehung wird „has-a“ genannt und die Integration ist eine „is-a“-Beziehung.

Aufgrund der fehlenden expliziten Zusammensetzung (Flexibilität im Vergleich zur Vererbung) ist das Hinzufügen von Zwischencode auch in Ordnung.

AOP-Funktionen

Als aspektorientierte Funktion können Sie Funktionsdekoratoren verwenden. Die ECMA-262-3-Spezifikation definiert das Konzept der „Funktionsdekoratoren“ nicht klar (im Gegensatz zu Python, wo dieser Begriff offiziell definiert ist). Allerdings können Funktionen mit funktionalen Parametern in bestimmten Aspekten dekoriert und aktiviert werden (durch Anwendung sogenannter Vorschläge):

Das einfachste Dekorationsbeispiel:

Code kopieren Der Code lautet wie folgt:

Funktion checkDecorator(originalFunction) {
Rückgabefunktion () {
If (fooBar != 'test') {
alarm('falscher Parameter');
Gibt false zurück;
}
Gibt originalFunction();
zurück };
}

Funktionstest() {
alarm('Testfunktion');
}

var testWithCheck = checkDecorator(test);
var fooBar = false;

test(); // 'Testfunktion'
testWithCheck(); // 'falscher Parameter'

fooBar = 'test';
test(); // 'Testfunktion'
testWithCheck(); // 'Testfunktion'

Fazit

In diesem Artikel haben wir die Einführung von OOP erläutert (ich hoffe, diese Informationen waren für Sie hilfreich) und im nächsten Kapitel werden wir mit der Implementierung von ECMAScript für die objektorientierte Programmierung fortfahren.

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn