OOP - Vererbung
Einführung am Beispiel von verschiedenen Würfeln
Programmidee:
Bekanntlich gibt es sehr viele verschiedene Würfelarten, z.B. Würfel mit 4, 6 ... Seiten.
Hier zur Vorstellung ein Bild.
Wir wollen nun diese Würfel in einem Computerprogramm implementieren und dabei so
'geschickt' wie möglich beim Programmieren der einzelnen Würfel vorgehen.
Modellierung der verschiedenen Würfelklassen
Beobachtung:
Am obigen Bild sehen wir, dass die verschiedenen Klassen, die die jeweiligen
Würfel darstellen sollen, alle die gleichen Eigenschaften und Methoden haben. Einzig
bei der 'Würfelmethode'
unterscheiden sich die einzelnen Klassen voneinander, da ja jeder Würfeltyp eine andere
Anzahl an Seiten hat. Ferner könnte man noch erwähnen, dass natürlich auch die Namen der
Klassen verschieden sind.
Wenn wir also nun die Klassen später implementieren wollen, so ist es
wohl unsinnig, jede einzelne Klasse einzeln zu schreiben. Dies hätte nämlich
einen erheblichen Mehraufwand zur Folge. Wir wollten ja aber unsere Würfel so 'geschickt' wie
möglich programmieren.
Also gibt es nun zwei Möglichkeiten:
Entweder wir
- schreiben einen Universalwürfel, bei dem man die Anzahl der Seiten festlegen kann.
oder wir
- schreiben einen allgemeineren Würfel, der die gemeinsamen Eigenschaften und Methoden
aller Würfel in sich vereint.
Die unterschiedliche Würfelmethode für jeden Würfeltyp könnte man dann jeweils
in einer eigenen 'Würfelklasse' (z.B TWuerfel ) implementieren.
'Verwirklichung' der Würfel
Klassendiagramm
Wie schon oben erwähnt haben wir einen 'allgemeinen' Würfel und dann die verschiedenen
'Abkömmlinge', also Würfel mit 4, 6 usw. Seiten, die alle eine eigene wuerfele-Methode haben,
und dennoch auf die Eigenschaften und Methoden der Basisklasse zugreifen können.
Einfaches Beispielprogramm mit Vererbung
Modellierung
siehe Klassendiagramm oben
Prototyp
Download
Implementation von TZahl
UNIT mtZahl;
interface
//-------------------- ggf Uses-Liste einfügen ! --------------------
//uses ....;
type
tZahl = CLASS tZahl als neues Objekt definieren
// weitere Attribute
private
FZahl : integer; FZahl als neues Attribut definieren
// weitere Methoden
public
procedure setzeFZahl(pFZahl: integer); setzeFZahl als neue Prozedur definieren
function liesFZahl : integer; liesFZahl als neue Funktion definieren
end;
implementation
//+---------------------------------------------------------------------
//| tZahl: Methodendefinition
//+---------------------------------------------------------------------
//-------- setzeFZahl (public) -----------------------------------------
procedure tZahl.setzeFZahl(pFZahl: integer);
begin
FZahl := pFZahl setzen von FZahl
end;
//-------- liesFZahl (public) ------------------------------------------
function tZahl.liesFZahl : integer;
begin
result := FZahl zurückgeben von FZahl
end;
end.
Implementation von TWuerfel
UNIT mTWuerfel;
interface
//-------------------- ggf Uses-Liste anpassen ! --------------------
uses mTZahl;
type
TWuerfel = CLASS(TZahl) Die Klasse TZahl wird vererbt
// weitere Methoden
public
procedure wuerfel; Methode "wuerfel"
end;
implementation
//+---------------------------------------------------------------------
//| TWuerfel: Methodendefinition
//+---------------------------------------------------------------------
//-------- wuerfel (public) --------------------------------------------
procedure TWuerfel.wuerfel;
begin
setzeFzahl(random(6)+1); Eine Zufallszahl zwischen 1 und 6 wird erzeugt
end;
initialization
Randomize; Der Zufallszahlgenerator wird neu gesetzt
end.
|
Erklärung
Die Klasse TWuerfel erbt von der Klasse TZahl, d.h. sie kann nun alle Methoden
der Klasse TZahl benutzen (SetzeZahl, liesZahl,...).
Zusätzlich kann die Klasse TWuerfel auch eigene Attribute und Methoden haben
(wuerfel).
Implementation der GUI
unit uVererbung0;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, mtWuerfel, StdCtrls;
type
TGUI = class(TForm)
bWuerfele: TButton;
lWurf: TLabel;
procedure FormCreate(Sender: TObject);
procedure bWuerfeleClick(Sender: TObject);
private
wuerfel1 : tWuerfel; Refernzvariable vom Typ TWürfel
procedure updateGUI; Prozedur zum aktualisieren der GUI
public
{ Public-Deklarationen }
end;
var
GUI: TGUI;
implementation
{$R *.dfm}
procedure TGUI.updateGUI;
var
w : integer;
begin
w := wuerfel1.liesFZahl; Die Augenzahl des Würfels wird geholt
lWurf.Caption := IntToStr(w); Der Wert wird auf das Formular ausgegeben
end;
procedure TGUI.FormCreate(Sender: TObject);
begin
wuerfel1 := tWuerfel.Create; Ein Objekt der Klasse TWuerfel wird erzeugt
Randomize; Der Zufallsgenerator wird initialisiert
wuerfel1.wuerfele; Der Würfel wird gewürfelt
updateGUI;
end;
procedure TGUI.bWuerfeleClick(Sender: TObject);
begin
wuerfel1.wuerfele; Der Würfel wird gewürfelt
updateGUI; Das Formular wird aktualisiert
end.
polymorphe Variablen
Der Begriff polymorph kommt aus dem Griechischen und bedeutet etwa so viel wie 'Vielgestaltigkeit' oder
'Mehrförmigkeit'. Polymorph heißt in diesem Fall, dass eine Variable von eines bestimmten Types nicht nur
Objekte ihres Typs, sondern auch von den abgeleiteten Subtypen enthalten kann. Im folgenden wird dies durch
ein Programm dargestellt, mit dem man einem Würfel auswählen und mit diesem auch würfeln kann.
Screenshot
Auszug aus dem Quellcode
procedure TForm1.bW6aktuellClick(Sender: TObject);
begin
wAktuell := w6;
lAktuell.caption := 'W6 ist aktueller Würfel';
end;
procedure TForm1.bWuerfeleClick(Sender: TObject);
begin
if wAktuell = w4 then
TW4(wAktuell).wuerfele;
if wAktuell = w6 then
TW6(wAktuell).wuerfele;
lAugen.Caption := IntToStr(wAktuell.GetZahl);
end;
Quelltext zum Download
Anmerkungen:
- In dem Programm gibt es 2 Würfel (TW4 und TW6), die beide von der Klasse
TWuerfel abgeleitet wurden. TW4 hat 4, TW6 6 Seiten.
- wAktuell ist vom Typ TWuerfel und enthält das Objekt, mit dem 'gewürfelt' werden soll.
Wie wir in die Prozedur bW6aktuellClick sehen, so sehen wir etwas ungewöhnliches:
Der Variable wAktuell vom Typ TWuerfel wird ein Objekt vom Typ TW6 zugeordnet.
Wie soll das gehen? Wenn wir in früheren Zeiten versucht haben, z.B. eine Zahl
vom Typ Integer einer Variable vom Type String zuzuordnen, so haben wir eine
'saftige' Fehlermeldung geerntet.
Wieso hier nicht?
Man kann einer Variable ein Objekt anderen Typs zuordnen, wenn dieses Objekt
von dem Typ der eigentlichen Variable abgeleitet ist.
In unserem Beispiel können wir also der Variable wAktuell vom Typ TWuerfel
alle Objekte zuweisen, die von TWuerfel abgeleitet sind, also TW4 und TW6.
Das heißt konkret: obschon eine Variable einen Wert oder ein Objekt bestimmten
Types erwartet, kann dieser oder dieses jederzeit von einem abgeleiteten Typ
(Subtyp) ersetzt werden. Umgekehrt funktioniert dies aber nicht, da ja der Subtyp
eine Spezialisierung von der Basisklasse ist. Das wäre genauso, wenn man eine Klasse
TAuto hätte und würde dieser einer Referenzvariable vom Typ TVW, die ganz
bestimmte Eigenschaften hat, zuordnen. Visualisiert bedeutet dies:
Man sieht hier, dass die Eigenschaften Marke und Extras (wobei es noch viele mehr geben
könnte...) spezialisiert sind, d.h. sie kommen in der Basisklasse überhaupt nicht vor.
Wenn man also nun versucht, ein Objekt von TAuto einer Variable vom Typ TVW zuzuordnen,
so gelingt dies nicht, weil die Deklarartionen nicht übereinstimmen, da der Typ TAuto
gar keine Marke oder Extras hat. Andersherum funktioniert es (wenn wir TVW TAuto zuweisen
wollen), da die Eigenschaften Marke und Extras sozusagen vorübergehend 'maskiert' werden, es sind also
nur die Eigenschaften von TAuto zugreifbar. Die 'maskierten'Eigenschaften und Methoden kann man
wieder mit dem Typcasting 'enttarnen'.
Type-Casting
Das Type-Casting läst sich am besten mit unserem Beispiel zu polymorphen Variablen erklären.
Hier noch einmal der Auszug aus unserem
Beispielprogramm.
Auffällig erscheint die folgende Zeile aus der Prozedur 'bWuerfeleClick':
TW6(wAktuell).wuerfele;
Ungewohnt an dieser Code-Zeile ist dabei, dass wir offentsichtlich die Methode wuerfele
ausführen können.
Wir haben zwar wAktuell ein Objekt der Klasse
TW6 zugeordnet, aber wAktuell ist trotzdem nur vom Typ TWuerfel, der eigentlich
gar keine Methode wuerfele hat.
Der Clou an dieser Codezeile liegt an dem
TW6(wAktuell). Da wir der Variable wAktuell
ein Objekt der abgeleiteten Klasse TW6 zugeordnet haben, können wir dies später sozusagenwieder
rückgängig machen, indem wir einfach die Entsprechende Variable in den Namen der abgeleiteten Klasse
'einklammern'. Dieses 'Einklammern' bzw. Umwandeln nennt man in der Informatik
'Type-Casting'.
Das Type-Casting kann man mit folgenden Schema beschreiben:
* Subklasse: Andere Bezeichnung für eine abgeleitete Klasse.
Also: Wir können sozusagen eine abgeleiteteKlasse als eine Basisklasse maskieren, indem wir sie einfach
einer Variable vom Typ der Basisklasse zuordnen. Diesen Vorgang kann man mit dem Type-Casting, also
die Umklammerung mit dem Namen der abgeleiteten Klasse, wieder 'umkehren'.