Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

21 Alternativen zur Vererbung

21 Alternativen zur Vererbung

Ich hatte ja bereits erwähnt, das die Vererbung ein paar kleine Nachteile hat, wenn man Methoden als virtuell definiert. Zudem ist es auch nicht klug, z.B. das Zeichnen von Elementen auf verschiedene Methoden zu verteilen (es würden ja wieder langsame Funktionsaufrufe im Hintergrund stattfinden und zusammenhängender Code wird auseinandergerissen - das erschwert die Fehlersuche). Deswegen möchte ich Ihnen in diesem Kapitel zwei weitere Konzepte vorstellen.

Zum Seitenanfang
Zum Inhaltsverzeichnis

21.3 Mischformen

21.3 Mischformen

Eine strikte Trennung zwischen Aggregation und Komposition sorgt zwar für ein sauberen Aufbau und hat viel Stiel, aber irgendwie sieht die Realität anders aus. Das fängt schon damit an, dass man oft mehrere gleichartige Objekte komponieren muss. Dies ist dann aber streng genommen keine Aggregation mehr. Genauso ist es bei der Aggregation hinderlich (und auch nicht sehr performant), wenn man immer erst Prüfen muss, ob es das Objekt gibt. Oft sieht man, dass zwar ein Aggregationsmechanismus angeboten wird, aber wenn man den Standardkonstruktor aufruft (und somit nichts übergibt), wird das Unterobjekt intern erzeugt und auch nur in diesem Fall freigegeben. Somit reduziert man die Prüfungen und ist flexibler.

Zum Seitenanfang
Zum Inhaltsverzeichnis

21.1 Aggregation

21.1 Aggregation

In diesem Kapitel geht es um das Einbinden von Objekten innerhalb von Objekten, denn genau dies ist mit Aggregation gemeint. Eine Aggregation ist immer eine 1 zu n Beziehung mit einem anderen Objekt, wobei jenes nicht zwangsläufig existieren muss. Im UML Diagramm werden diese Beziehungen mit einer Raute an der Relationslinie gekennzeichnet, wobei die Raute an jenem Objekt steht, welche den Container darstellt. Zudem schreibt man an die Relationslinien die entsprechenden Kardinalitäten, also auf deutsch gesagt, das Verhältnis der Beziehung (z.B. 1 zu 1, 1 zu n oder n zu m - wobei n auch oft durch ein * ausgedrückt wird).

Veranschaulichung einer Aggregation

In obiger Grafik sehen Sie, dass die Klasse "CFahrzeug" die Objekte der Klasse "CRaeder" aggregiert. Das hat jetzt mehrere Vorteile. Mir ist es jetzt möglich ein Fahrzeug ohne Räder zu erzeugen. Das ist praktisch, falls ich später ein Boot implementieren möchte, was in der Regel keine Räder hat. Genauso kann ich die Räder vom Fahrzeug im Nachhinein lösen (z.B für ein Unfall bei welchem das Fahrzeug die Räder verliert) und/oder ersetzen (z.B. bei einem Boxenstopp).

Was haben nun die Kardinalitäten zu sagen? Man muss sie immer beidseitig betrachten, also aus beiden Richtungen. Jedes Rad ist nur einem Fahrzeug zugeordnet, aber jedem Fahrzeug können keine oder mehrere Räder zugeordnet seien. Statt dem "n" hätte ich auch vier nehmen können, aber wenn ich später einen LKW mit aufnehmen möchte, bräuchte ich definitiv mehr als vier Räder und deswegen habe ich es allgemein gehalten.

Aber was bedeutet dies jetzt für die Implementierung? Schauen wir uns zunächst die Header-Datei an.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
					
// Basisklasse für alle Fahrzeuge
class CFahrzeug {
	private:
		CRaeder**	m_pRaeder;
		int 		m_iRadanzahl;

		// ...

	public:
		// Benutzerdefinierter Konstruktor
		CFahrzeug(CRaeder** pReader = NULL, int iRadanzahl = 0);

		// ...
};
					

Wie man sehr schön erkennen kann, befindet sich in der Klasse ein Zeiger auf ein Array der mit Zeigern auf Räder. Zeiger sind für Aggregationen üblich, da die Objekte (bzw. ihre Referenz) dem Konstruktor übergeben werden (auch optional noch später über Set-Methoden). Alternativ könnten sie ja auch nicht vorhanden sein (es wird NULL übergeben), und dies kann man nur durch Pointer realisieren. Die Speicherverwaltung wird außerhalb der Klasse vorgenommen.

Nun zur Implementierung in der zugehörigen CPP Datei des Fahrzeuges.

 1
 2
 3
 4
 5
 6
 7
 8
 9
					
#include "Fahrzeug.h"



// Benutzerdefinierter Konstruktor
CFahrzeug::CFahrzeug(CRaeder** pRaeder, int iRadanzahl)
	: m_pRaeder(pRaeder)
	, m_iRadanzahl(iRadanzahl)
{} ////////////////////////////////////////////////////////////////////////////
					

Was hier passiert, dürfte nicht weiter spannend sein. Im benutzerdefinierten Konstruktor werden die entsprechenden Komponenten übergeben und zugewiesen. Einen Destruktor gibt es nicht, da die Speicherverwaltung außerhalb des Objektes stattfindet und so nichts freigegeben werden muss.

Bei aggregierten Objekten muss man also vor jeder Verwendung (also in allen Methoden, welche auf diese Objekte zugreifen wollen) erst prüfen, ob ein gültiger Zeiger vorliegt, bevor man ihn dereferenziert, um auf seine Member zuzugreifen.

Zum Seitenanfang
Zum Inhaltsverzeichnis

21.2 Komposition

21.2 Komposition

Die Komposition ist fast das Gleiche wie die Aggregation, nur handelt es sich hier üblicherweise um eine strikte 1 zu 1 Beziehung. Mit strikt meine ich, dass das Containerobjekt nicht ohne das komponierte Objekt auskommt bzw. lebensfähig ist. Während ein Mensch z.B. seine Kleider aggregiert (man hat sie nicht immer an und wenn man sie an hat, dann sind es solche, die erst weit nach der eigenen Geburt produziert und ihm Laden übergeben wurden), ist die Beziehung zu seinem Herzen eine Komposition. Der Mensch kann nur mit Herz existieren. Es entsteht mit seiner Entstehung und es hört genau dann auf zu schlagen, wenn er stirbt. Das zeigt uns, das bei einer Komposition das Containerobjekt selbst die Steuerung und somit die Speicherverwaltung vornimmt. üblicherweise benutzt man hier keine Zeiger. Sie werden im Konstruktor erzeugt und im Destruktor freigegeben. In UML wird diese Beziehung ebenfalls mit einer Linie und einer Raute daran ausgedrückt, nur mit dem Unterschied, dass die Raute ausgemalt ist.

Die meisten Relationen meines Beispieles sind Kompositionen, da ich Zeiger und somit unnötige Indirektionen umgehen möchte. Zudem macht es oft auch wenig Sinn, ein Motorrad ohne Kette oder Lenker zu haben (zu mindestens in einem Computerspiel).

Veranschaulichung einer Komposition

Schauen wir uns nun die Definition in der Header-Datei an.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
					
#include "Fahrzeug.h"



// Basisklasse für alle Motorräder
class CMotorrad : public CFahrzeug {
	private:
		CLenker		m_oLenker;
		CKette		m_oKette;

		// ...

	public:
		// Benutzerdefinierter Konstruktor
		CMotorrad(int iLenkerart);

		// ...
};
					

Und jetzt die Implementierung in der CPP Datei.

 1
 2
 3
 4
 5
 6
 7
 8
 9
					
#include "Motorrad.h"



// Benutzerdefinierter Konstruktor ////////////////////////////////////////////
CMotorrad::CMotorrad(int iLenkerart)
	: m_oLenker(iLenkerart)
	, m_oKette()
{} ////////////////////////////////////////////////////////////////////////////
					

Wie Sie sehen können, benutze ich keine Zeiger und die Objekte werden im Konstruktor erzeugt. Hätte ich mich auch hier für Zeiger auf die komponierten Objekte entschieden, bräuchte ich also noch einen Destruktor, um den Speicher wieder freizugeben.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012