Viele Dinge im echten Leben haben gewisse Eigenschaften gemein. Hunde und Katzen sind beispielsweise beide Tiere. Auch
Objekte können gemeinsame Eigenschaften haben, die Sie mit Hilfe einer Generalisierung zwischen ihren Klassen
verdeutlichen können. Wenn Sie gemeinsame Merkmale in eigene Klassen extrahieren, lässt sich das System künftig
einfacher verwalten.
Eine Generalisierung zeigt, dass eine Klasse von einer anderen erbt. Die erbende Klasse wird Nachfahre (oder
untergeordnete Klasse) genannt. Die vererbende Klasse ist der Vorfahre (oder übergeordnete Klasse). Vererbung bedeutet,
dass die Definition des Vorfahren, einschließlich aller Eigenschaften wie Attribute, Beziehungen und Operationen für
die zugehörigen Objekte, auch für die Objekte des Nachfahren gilt. Die Generalisierung wird von der untergeordneten
Klasse (Nachfahre) hin zur übergeordneten Klasse (Vorfahre) gezeichnet.
Generalisierung kann in mehreren Stufen stattfinden, was Ihnen ermöglicht, komplexe, mehrschichtige
Vererbungshierarchien zu modellieren. Allgemeine Eigenschaften können in den oberen Teil der Vererbungshierarchie und
besondere Eigenschaften in den unteren Teil gestellt werden. Anders ausgedrückt, Sie können mit Generalisierungen
spezialisierte Versionen eines allgemeineren Konzepts modellieren.
Beispiel
In dem System "Recycling-Maschine" beschreiben alle Klassen - Dose, Flasche und Fass - unterschiedliche Typen von
Pfandartikeln. Abgesehen von der Tatsache, dass alle Pfandartikel sind, haben sie noch zwei gemeinsame Eigenschaften:
Höhe und Gewicht. Sie können diese Eigenschaften mit Attributen und Operationen in einer separaten Klasse, z. B.
Pfandartikel, modellieren. Dose, Flasche und Fass erben die Eigenschaften dieser Klasse.
Die Klassen Dose, Flasche und Fass haben die gemeinsamen Eigenschaften Höhe und Gewicht. Jede dieser Klassen ist eine
spezialisierte Version des allgemeinen Konzepts "Pfandartikel".
Mit Mehrfachvererbung kann eine Klasse Eigenschaften von mehreren anderen Klassen erben, obwohl dies im Allgemeinen
eher die Ausnahme ist.
Bei der Verwendung von Mehrfachvererbung müssen diverse potenzielle Probleme berücksichtigt werden:
-
Wenn die Klasse von mehreren Klassen erbt, müssen Sie prüfen, wie die Beziehungen, Operationen und Attribute in den
Vorfahren benannt sind. Sollte derselbe Name in mehreren Vorfahren vorkommen, müssen Sie beschreiben, was dieser
Name für die jeweilige erbende Klasse bedeutet, z. B. indem Sie den Namen mit der Deklarationsquelle qualifizieren.
-
Wenn Sie mit wiederholter Vererbung arbeiten, erbt ein Nachfahre in denselben Vorfahren mehrfach. In einem solchen
Fall ist die Vererbungshierarchie, wie im Folgenden gezeigt, mit einem Symbol in Form einer Raute gekennzeichnet.
Wiederholte Vererbung und Mehrfachvererbung. Die Klasse "Fenster mit Schiebeleiste und Dialogfenster" erbt die Klasse
"Fenster" mehrfach.
Eine Frage, die in diesem Kontext aufkommen kann, ist "Wie viele Kopien der Attribute von Fenster sind in Instanzen von
Fenster mit Schiebeleiste und Dialogfenster enthalten?". Wenn Sie mit wiederholter Vererbung arbeiten, müssen Sie eine
klare Definition der Semantik haben. In den meisten Fällen wird dies von der Programmiersprache definiert, die
Mehrfachvererbung unterstützt.
Im Allgemeinen sind die Regeln der Programmiersprache, die die Mehrfachvererbung steuern, sehr komplex und häufig
schwierig anzuwenden. Deshalb wird empfohlene, Mehrfachvererbung nur bei Bedarf und immer mit Vorsicht anzuwenden.
Eine Klasse, die nicht instanziert ist und nur als Vererbungsklasse für andere Klassen existiert, ist eine abstrakte
Klasse. Klassen, die instanziert werden, sind konkrete Klassen. Eine abstrakte Klasse muss mindestens einen Nachfahren
haben, um von Nutzen zu sein.
Beispiel
Ein Palettenbereich im Lagerverwaltungssystem ist eine abstrakte Entitätsklasse, die Eigenschaften darstellt, die
verschiedenen Typen Palettenbereichen gemein sind. Die Klasse wird von den konkreten Klassen Station, Transporter und
Lagereinheit geerbt, die alle Palettenbereiche in dem Lager sein können. Alle diese Objekte haben eine gemeinsame
Eigenschaft: Sie können eine oder mehrere Paletten aufnehmen.
Die geerbte Klasse, hier Palettenbereich, ist abstrakt und wird selbst nicht instanziert.
Da Klassenstereotypen unterschiedliche Zwecke haben, macht die Vererbung zwischen einem Klassenstereotyp und einem
keinen Sinn. Wenn Sie beispielsweise eine Grenzklasse eine Entitätsklasse erben lassen, würde aus der Grenzklasse eine
Art Hybridklasse. Deshalb sollten Generalisierungen nur zwischen Klassen desselben Stereotyps verwendet werden.
Sie können Generalisierungen verwenden, um zwei Beziehungen zwischen Klassen auszudrücken:
-
Subtyping: Diese Art von Beziehung gibt an, dass der Nachfahre ein Subtyp des Vorfahren ist. Die Erstellung von
Subtypen bedeutet, dass der Nachfahre die Struktur und das Verhalten des Vorfahren erbt und dass der Nachfahre ein
Typ des Nachfahren ist (d. h. der Nachfahre ist ein Subtyp, der in jeder Situation für alle seine Vorfahren
einspringen kann).
-
Subclassing: Diese Art von Beziehung gibt an, dass der Nachfahre eine Unterklasse (kein Subtyp) des Vorfahren ist.
Subclassing bedeutet, dass der Nachfahre die Struktur und das Verhalten des Vorgänger erbt und dass der Nachfahre
kein Typ des Vorfahren ist.
Sie können solche Beziehungen erstellen, indem Sie gemeinsame Merkmale untergliedern und in separate Klassen stellen,
die die anderen erben, oder indem Sie neue Klassen erstellen, die eine spezialisierte Version allgemeinerer Klassen
sind, und diese von den allgemeinen Klassen erben lassen.
Wenn die zwei Varianten gleich sind, es ist nicht schwierig, die richtige Vererbung zwischen Klassen einzurichten.
Manchmal sind die Varianten jedoch nicht gleich, und Sie müssen darauf achten, dass die Vererbung verständlich bleibt.
Zumindest müssen Sie den Zweck jeder Vererbungsbeziehung im Modell kennen.
Subtyping bedeutet, dass der Nachfahre ein Subtyp ist, der jederzeit für alle seine Vorfahren einspringen kann.
Subtyping ist eine spezielle Form der Polymorphie und eine wichtige Eigenschaft, da Sie mit ihr alle Clients (Objekte,
die den Vorfahren verwenden) entwerfen können, ohne die potenziellen Nachfahren berücksichtigen zu müssen. Dies macht
die Clientobjekte allgemeiner und wiederverwendbar. Wenn der Client das eigentliche Objekt verwendet, geht er dabei auf
eine spezielle Weise vor, aber das Objekt führt immer seine Aufgabe aus. Durch Subtyping kann sichergestellt werden,
dass das System Änderungen in der Gruppe der Subtypen toleriert.
Beispiel
In einem Lagerverwaltungssystem definiert die Klasse Transporterschnittstelle die grundlegende Funktionalität für die
Kommunikation mit allen Typen von Transporteinrichtungen, z. B. Kränen und LKW. Die Klasse definiert unter anderem die
Operation executeTransport.
Die Klassen LKW-Schnittstelle und Kranschnittstelle erben von der Transporterschnittstelle, d. h. Objekte beider
Klassen antworten auf die Nachricht executeTransport. Die Objekte können jederzeit für die Transporterschnittstelle
einspringen und dasselbe Verhalten bieten. Somit können andere Objekte (Clientobjekte) eine Nachricht an ein Objekt von
Transporterschnittstelle senden, ohne zu wissen, ob ein Objekt von LKW-Schnittstelle oder Kranschnittstelle auf die
Nachricht antwortet.
Die Klasse Transporterschnittstelle kann auch eine abstrakte Schnittstelle sein, die selbst nicht instanziert wird. In
diesem Fall kann die Transportschnittstelle nur die Deklaration der Operation executeTransport definieren, während die
untergeordneten Klassen (Nachfahren) die Operation implementieren.
Einige objektorientierte Sprachen wie C++ verwenden die Klassenhierarchie als eine Typhierarchie und zwingen den
Designer damit, Vererbung für Subtyping im Designmodell einzusetzen. Andere Sprachen wie Smalltalk-80 verwenden zur
Kompilierzeit keine Typprüfung. Wenn die Objekte auf eine empfangene Nachricht nicht antworten können, generieren Sie
eine Fehlernachricht.
Es empfiehlt sich unter Umständen, auch in Sprachen ohne Typprüfung mit Generalisierung zu arbeiten, um
Subtypbeziehungen anzuzeigen. In einigen Fällen sollten Sie unabhängig davon, ob die Sprache es zulässt,
Generalisierung einsetzen, damit das Objektmodell und der Quellcode verständlicher werden und einfacher zu verwalten
sind. Ob diese Verwendung von Vererbung dem guten Stil entspricht, richtet sich in erster Linie nach den Konventionen
der Programmiersprache.
Subclassing macht den Wiederverwendungsaspekt von Generalisierungen aus. Wenn Sie mit Subclassing arbeiten, entscheiden
Sie, welche Teile einer Implementierung Sie wiederverwenden können, indem Sie die Vererbung von Eigenschaften
festlegen, die von anderen Klassen definiert werden. Mit Subclassing können Sie Aufwand einsparen und Code
wiederverwenden, wenn Sie eine bestimmte Klasse implementieren.
Beispiel
In der Klassenbibliothek von Smalltalk-80 erbt die Klasse Dictionary Eigenschaften von Set.
Der Grund für diese Generalisierung ist der, dass Dictionary einige allgemeine Methoden und Speicherstrategien von der
Implementierung von Set wiederverwenden kann. Selbst wenn ein Dictionary als ein Set (das Schlüssel/Wert-Paare enthält)
gesehen werden könnte, ist Dictionary kein Subtyp von Set, da Sie nicht einfach einen beliebigen Typ von Objekt zu
einem Dictionary (nur Schlüssel/Wert-Paare) hinzufügen können. Objekte, die Dictionary verwenden, wissen nicht, dass
dass das Dictionary eigentlich ein Set ist.
Subclassing führt häufig zu unlogischen Vererbungshierarchien, die schwierig zu verstehen und zu verwalten sind.
Deshalb wird davon abgeraten, Vererbung nur zum Zwecke der Wiederverwendung zu verwenden, sofern keine anderslautende
Empfehlung für die Verwendung der Programmiersprache vorgegeben ist. Die Verwaltung dieser Art von Wiederverwendung ist
in der Regel etwas knifflig. Jede Änderung in der Klasse Set kann umfangreiche Änderungen aller Klassen nach sich
ziehen, die die Klasse Set erben. Bedenken Sie dies und vererben Sie nur stabile Klassen. Bei Vererbung wird die
Implementierung der Klasse Set eingefroren, da Änderungen an dieser Klasse zu kostenintensiv sind.
Die Verwendung von Generalisierungsbeziehungen im Design muss sich maßgeblich nach der Semantik und der empfohlenen
Verwendung von Vererbung in der Programmiersprache richten. Objektorientierte Sprachen unterstützen Vererbung zwischen
Klassen. Nicht objektorientierte Sprachen tun dies nicht. Sie müssen Sprachmerkmale im Designmodell berücksichtigen.
Wenn Sie eine Sprache verwenden, die Vererbung oder Mehrfachvererbung nicht unterstützt, müssen Sie die Vererbung in
der Implementierung simulieren. In diesem Fall ist es besser, die Simulation im Designmodell zu modellieren und auf
Generalisierungen zu verzichten, um die Vererbungsstrukturen zu beschreiben. Die Modellierung von Vererbungsstrukturen
mit Generalisierungen und anschließende Simulation der Vererbung in der Implementierung kann das Design ruinieren.
Wenn Sie eine Sprache verwenden, die Vererbung oder Mehrfachvererbung nicht unterstützt, müssen Sie die Vererbung in
der Implementierung simulieren. In diesem Fall empfiehlt es sich, die Simulation im Designmodell zu modellieren und auf
Generalisierungen zu verzichten, um die Vererbungsstrukturen zu beschreiben. Wenn Sie Vererbungsstrukturen mit
Generalisierungen modellieren und anschließend nur die Vererbung in der Implementierung simulieren, kann dies das
Design ruinieren.
Wahrscheinlich müssen Sie die Schnittstellen und weitere Objekteigenschaften während der Simulation ändern. Es wird
empfohlen, Vererbung mit einer der folgenden Methoden zu simulieren:
-
Lassen Sie den Nachfahren Nachrichten an den Vorfahren weiterleiten.
-
Duplizieren Sie den Code des Vorfahren in jedem Nachfahren. In diesem Fall wird keine Vorfahrenklasse erstellt.
Beispiel
In diesem Beispiel leiten die Nachfahren Nachrichten über Links, die Instanzen von Assoziationen sind, an den Vorfahren
weiter.
Verhalten, das Dose, Flasche und Fass gemein ist, wird einer speziellen Klasse zugeordnet. Objekte, die dieses
Verhalten aufweisen, senden eine Nachricht an ein Objekt von Pfandartikel, um das Verhalten bei Bedarf zu erbringen.
|