JavaRush /Java-Blog /Random-DE /Vererbung als Phänomen
articles
Level 15

Vererbung als Phänomen

Veröffentlicht in der Gruppe Random-DE
Ehrlich gesagt habe ich diesen Artikel ursprünglich nicht geplant. Ich hielt die Themen, die ich hier diskutieren möchte, für trivial und nicht einmal der Erwähnung wert. Während ich jedoch Artikel für diese Website schrieb, stieß ich in einem der Foren auf eine Diskussion über Mehrfachvererbung. Dabei stellte sich heraus, dass die meisten Entwickler ein sehr vages Verständnis von Vererbung haben. Und dementsprechend macht er viele Fehler. Da Vererbung eines der wichtigsten Merkmale von OOP ist (wenn nicht das wichtigste!), habe ich beschlossen, diesem Phänomen einen eigenen Artikel zu widmen. * * * Zunächst möchte ich zwischen zwei Konzepten unterscheiden – Objekt und Klasse. Diese Konzepte werden ständig verwirrt. Mittlerweile sind sie von zentraler Bedeutung für OOP. Und meiner Meinung nach ist es notwendig, die Unterschiede zwischen ihnen zu kennen. Also das Objekt. Im Grunde ist es alles. Hier ist der Würfel. Holz, blau. Die Kantenlänge beträgt 5 cm. Es handelt sich hierbei um ein Objekt. Und da ist eine Pyramide. Kunststoff, rot. 10 cm Rippe. Auch das ist ein Objekt. Was haben Sie gemeinsam? Verschiedene Größen. Andere Form. Anderes Material. Sie haben jedoch etwas gemeinsam. Erstens sind sowohl der Würfel als auch die Pyramide regelmäßige Polyeder. Diese. Die Summe aus der Anzahl der Eckpunkte und der Anzahl der Flächen ist um 2 größer als die Anzahl der Kanten. Weiter. Beide Formen haben Flächen, Kanten und Scheitelpunkte. Beide Figuren haben ein Merkmal wie die Rippengröße. Beide Formen können gedreht werden. Beide Figuren können gezeichnet werden. Die letzten beiden Eigenschaften sind bereits Verhalten. Und so weiter. Die Programmierpraxis zeigt, dass es viel einfacher ist, mit homogenen Objekten zu arbeiten als mit heterogenen. Und da zwischen diesen Figuren immer noch etwas gemeinsam ist, besteht der Wunsch, diese Gemeinsamkeit irgendwie hervorzuheben. Hier kommt das Konzept der Klasse ins Spiel. Also die Definition.
Eine Klasse ist ein Deskriptor der gemeinsamen Eigenschaften einer Gruppe von Objekten. Diese Eigenschaften können sowohl Eigenschaften von Objekten (Größe, Gewicht, Farbe usw.) als auch Verhaltensweisen, Rollen usw. sein.
Kommentar. Das Wort „alle“ (Beschreibung aller Eigenschaften) wurde nicht gesprochen. Das bedeutet, dass jedes Objekt mehreren verschiedenen Klassen angehören kann. Vererbung als Phänomen - 1 Nehmen wir das gleiche Beispiel mit geometrischen Formen als Grundlage. Die allgemeinste Beschreibung ist regelmäßiges Polyeder . Unabhängig von Kantengröße, Anzahl der Flächen und Eckpunkte. Wir wissen nur, dass diese Figur Eckpunkte, Kanten und Flächen hat und dass die Längen der Kanten gleich sind. Weiter. Wir können die Beschreibung konkreter gestalten. Nehmen wir an, wir möchten dieses Polyeder zeichnen . Lassen Sie uns ein solches Konzept als gezeichnetes regelmäßiges Polyeder einführen . Was brauchen wir zum Zeichnen? Beschreibung einer allgemeinen Zeichenmethode, die nicht von bestimmten Scheitelpunktkoordinaten abhängt. Vielleicht die Farbe des Objekts. Nun stellen wir die Klassen Cube und Tetrahedron vor . Zu diesen Klassen gehörende Objekte sind sicherlich regelmäßige Polyeder. Der einzige Unterschied besteht darin, dass die Anzahl der Eckpunkte, Kanten und Flächen für jede der neuen Klassen bereits streng festgelegt ist. Wenn wir außerdem den Typ einer bestimmten Figur kennen, können wir die Zeichenmethode beschreiben. Dies bedeutet, dass jedes Objekt der Würfel- oder Tetraeder -Klasse auch ein Objekt der gezeichneten regulären Polyeder- Klasse ist . Es gibt eine Klassenhierarchie. In dieser Hierarchie steigen wir von der allgemeinsten Beschreibung zur spezifischsten ab. Beachten Sie, dass ein Objekt einer beliebigen Klasse auch zur Beschreibung einer allgemeineren Klasse in der Hierarchie passt. Diese Klassenbeziehung wird Vererbung genannt . Jede untergeordnete Klasse erbt alle allgemeineren Eigenschaften der übergeordneten Klasse und fügt diesen Eigenschaften (möglicherweise) einige eigene hinzu. Oder es überschreibt einige Eigenschaften der übergeordneten Klasse. Hier möchte ich aus Gradi Buchas klassischem Buch über objektorientiertes Design zitieren:
Vererbung definiert daher eine „ist eine“-Hierarchie zwischen Klassen, in der die Unterklasse von einer oder mehreren Oberklassen erbt. Dies ist in der Tat der Lackmustest für die Vererbung. Wenn A bei den Klassen A und B „keine“ Art von B ist, sollte A keine Unterklasse von B sein.
Übersetzt klingt es so:
Vererbung definiert somit eine „ist“-Hierarchie zwischen Klassen, in der eine Unterklasse von einer oder mehreren Oberklassen erbt. Dies ist in der Tat der entscheidende Test (im wahrsten Sinne des Wortes ein Lackmustest, meine Anmerkung) für die Vererbung. Wenn wir die Klassen A und B haben und wenn Klasse A keine Variante der Klasse B „ist“, dann darf A keine Unterklasse von B sein.
Wer bis hierhin gelesen hat, wird wahrscheinlich verwirrt mit dem Finger an der Schläfe herumwirbeln. Der erste Gedanke ist, dass das trivial ist! So ist das. Aber wenn Sie wüssten, wie viele verrückte Vererbungshierarchien ich gesehen habe! In der Diskussion, die ich gleich zu Beginn erwähnt habe, hat einer der Teilnehmer ganz ernsthaft einen Panzer geerbt von... einem Maschinengewehr!!! Aus dem einfachen Grund, dass der Panzer ein Maschinengewehr hat. Und das ist der häufigste Fehler. Vererbung wird mit Aggregation verwechselt – dem Einschluss eines Objekts in ein anderes. Der Panzer ist kein Maschinengewehr, er enthält eines. Und aufgrund dieses Fehlers besteht meistens der Wunsch, Mehrfachvererbung zu verwenden. Kommen wir nun direkt zu Java. Was gibt es in Sachen Vererbung? Es gibt zwei Arten von Klassen in der Sprache: solche, die eine Implementierung enthalten können, und solche, die dazu nicht in der Lage sind. Letztere werden Interfaces genannt, obwohl es sich im Kern um völlig abstrakte Klassen handelt. Vererbung als Phänomen - 2 Die Sprache ermöglicht es Ihnen also, eine Klasse von einer anderen Klasse zu erben, die möglicherweise eine Implementierung enthält. ABER NUR VON EINEM! Lassen Sie mich erklären, warum dies getan wurde. Der Punkt ist, dass jede Implementierung nur mit ihrem Teil umgehen kann – mit den Variablen und Methoden, die ihr bekannt sind. Und selbst wenn wir die Klasse C von A und B erben , dann kann die von der Klasse A geerbte Methode ProcessA nur mit der internen Variablen a arbeiten , da sie nichts über b weiß , genauso wie sie nichts über c und die Methode ProcessC weiß . Ebenso die Methode „processB“ . kann nur mit der Variablen b arbeiten. Das heißt im Wesentlichen, dass die vererbten Teile isoliert werden. Klasse C kann sicherlich mit ihnen arbeiten, aber sie kann genauso gut mit diesen Teilen funktionieren, wenn sie einfach als Teil davon aufgenommen und nicht vererbt werden. Allerdings gibt es hier noch ein weiteres Ärgernis, nämlich die Namensüberschneidung. Wenn die Methoden ProzessA und ProzessB denselben Namen hätten, beispielsweise Prozess, welchen Effekt hätte dann der Aufruf der Prozessmethode der Klasse C ? Welche der beiden Methoden würde aufgerufen werden? Natürlich verfügt C++ in dieser Situation über Steuerelemente, aber das trägt nicht zur Harmonie der Sprache bei. Die Implementierungsvererbung bietet also keine Vorteile, aber auch Nachteile. Aus diesem Grund wurde auf diese Implementierungsvererbung in Java verzichtet. Den Entwicklern blieb jedoch eine solche Möglichkeit zur Mehrfachvererbung als Vererbung von einer Schnittstelle. In Java-Begriffen eine Schnittstellenimplementierung. Was ist die Schnittstelle? Eine Reihe von Methoden. (Wir beschäftigen uns derzeit nicht mit der Definition von Konstanten in Schnittstellen; mehr dazu hier .) Was ist eine Methode? Und im Kern bestimmt eine Methode das Verhalten eines Objekts. Es ist kein Zufall, dass der Name fast jeder Methode eine Aktion enthält – getXXX , drawXXX , countXXX usw. Und da eine Schnittstelle eine Sammlung von Methoden ist, ist die Schnittstelle tatsächlich eine Determinante des Verhaltens . Eine weitere Möglichkeit zur Nutzung einer Schnittstelle besteht darin, die Rolle eines Objekts zu definieren. Beobachter, Zuhörer usw. In diesem Fall ist die Methode tatsächlich die Verkörperung einer Reaktion auf ein äußeres Ereignis. Das ist wiederum Verhalten. Ein Objekt kann natürlich verschiedene Verhaltensweisen haben. Wenn es gerendert werden muss, wird es gerendert. Wenn er gerettet werden muss, wird er gerettet. Na ja, usw. Dementsprechend ist die Möglichkeit, von Klassen zu erben, die das Verhalten definieren, sehr, sehr nützlich. Ebenso kann ein Objekt mehrere unterschiedliche Rollen haben. Allerdings UmsetzungDas Verhalten liegt ausschließlich im Gewissen der Kinderklasse. Die Vererbung von einer Schnittstelle (ihrer Implementierung) besagt, dass ein Objekt dieser Klasse in der Lage sein sollte, dies und das zu tun. Und WIE dies geschieht, wird von jeder Klasse, die die Schnittstelle implementiert, unabhängig bestimmt. Kehren wir zu Fehlern bei der Vererbung zurück. Meine Erfahrung bei der Entwicklung verschiedener Systeme zeigt, dass Sie mit der Vererbung von Schnittstellen jedes System implementieren können, ohne die Vererbung mehrerer Implementierungen zu verwenden. Und wenn ich daher auf Beschwerden über das Fehlen der Mehrfachvererbung in der Form stoße, wie sie in C++ existiert, ist das für mich ein sicheres Zeichen für falsches Design. Der häufigste Fehler ist der, den ich bereits erwähnt habe: Vererbung wird mit Aggregation verwechselt. Manchmal geschieht dies aufgrund falscher Annahmen. Diese. Nehmen wir zum Beispiel einen Tachometer: Es wird argumentiert, dass Geschwindigkeit nur durch Messung von Entfernung und Zeit gemessen werden kann. Danach wird der Tachometer erfolgreich vom Lineal und der Uhr geerbt und wird somit zum Lineal und zur Uhr, gemäß der Definition von Nachlass. (Meine Anfragen, die Zeit mit einem Tacho zu messen, wurden meist mit Witzen beantwortet. Oder sie antworteten überhaupt nicht.) Was ist hier der Fehler? In der Prämisse. Tatsache ist, dass der Tacho keine Zeit misst. Und Entfernungen übrigens auch. Der Kilometerzähler, der in jedem Tacho zu finden ist, ist ein klassisches Beispiel für ein zweites Gerät im gleichen Gehäuse, also Anhäufung. Es ist nicht erforderlich, die Geschwindigkeit zu messen. Es kann komplett entfernt werden – die Geschwindigkeitsmessung wird dadurch in keiner Weise beeinträchtigt. Manchmal werden solche Fehler absichtlich gemacht. Das ist viel schlimmer. „Ja, ich weiß, dass es falsch ist, aber für mich ist es bequemer.“ Wozu könnte das führen? Aber hier ist was: Wir werden einen Panzer von einer Kanone und einem Maschinengewehr erben. Auf diese Weise ist es bequemer. Dadurch wird der Panzer zu einer Kanone und einem Maschinengewehr. Als nächstes statten wir das Flugzeug mit zwei Maschinengewehren und einer Kanone aus. Was bekommen wir? Ein Flugzeug mit schwebenden Waffen in Form von drei Panzern! Denn es gibt definitiv jemanden, der, ohne es zu verstehen, einen Panzer als Maschinengewehr benutzt. Ausschließlich nach der Vererbungshierarchie. Und er wird völlig recht haben, denn derjenige, der eine solche Hierarchie entworfen hat, hat einen Fehler gemacht.
Im Allgemeinen verstehe ich den Ansatz „So ist es für mich bequemer“ nicht wirklich . Es ist bequem, wie ein Zuhörer zu schreiben, und diejenigen, die grundlegende Grammatik sagen, sind kazly. Ich übertreibe natürlich, aber der Grundgedanke bleibt bestehen: Zusätzlich zur momentanen Bequemlichkeit gibt es so etwas wie Alphabetisierung. Dieses Konzept basiert auf der Erfahrung einer sehr großen Anzahl von Menschen. Tatsächlich nennt man das im Englischen „Best Practice“ – die beste Lösung. Und oft bringen Lösungen, die einfacher erscheinen, in der Zukunft viele Probleme mit sich.
Dieses Beispiel ist natürlich stark übertrieben und daher absurd. Es gibt jedoch auch weniger offensichtliche Fälle, die dennoch katastrophale Folgen haben. Indem der Entwickler von einem Objekt erbt, anstatt es zu aggregieren, gibt er jedem die Möglichkeit, die Funktionalität des übergeordneten Objekts direkt zu nutzen. Mit allem, was dazugehört. Stellen Sie sich vor, Sie haben eine Klasse, die mit einer Datenbank arbeitet, DBManager . Mit DBManager - DataManager erstellen Sie eine weitere Klasse, die mit Ihren Daten arbeitet . Diese Klasse führt Datenkontrolle, Transformationen, zusätzliche Aktionen usw. durch. Im Allgemeinen eine Schicht zwischen der Business-Schicht und der Basisschicht. Wenn Sie DataManager von DBManager erben, hat jeder, der es verwendet, direkten Zugriff auf die Datenbank. Und deshalb wird er in der Lage sein, alle Aktionen unter Umgehung der Kontrolle, Transformationen usw. durchzuführen. Okay, gehen wir davon aus, dass niemand vorsätzlich Schaden anrichten möchte und direkte Aktionen kompetent sind. Aber! Nehmen wir an, dass sich die Basis geändert hat. Ich meine, einige Prinzipien der Kontrolle oder Transformation haben sich geändert. DataManager wurde geändert. Der Code, der zuvor direkt mit der Datenbank zusammengearbeitet hat, funktioniert jedoch weiterhin. Sie werden sich höchstwahrscheinlich nicht an ihn erinnern. Das Ergebnis wird ein Fehler dieser Art sein, dass diejenigen, die danach suchen, grau werden. Es würde niemandem in den Sinn kommen, dass er unter Umgehung des DataManagers mit der Datenbank arbeitet. Übrigens ein Beispiel aus dem wirklichen Leben. Es hat SEHR lange gedauert, den Fehler zu finden. Abschließend sage ich es noch einmal. Vererbung sollte NUR verwendet werden, wenn eine „ist“-Beziehung besteht. Denn das ist das eigentliche Wesen der Vererbung – die Fähigkeit, Objekte einer untergeordneten Klasse als Objekte einer Basisklasse zu verwenden. Wenn es keine „ist“-Beziehung zwischen Klassen gibt, SOLLTE KEINE Vererbung stattfinden!!! Nie und unter keinen Umständen. Und noch mehr – einfach weil es so praktisch ist. Link zur Originalquelle: http://www.skipy.ru/philosophy/inheritance.html
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION