HSG

Aktuelle Seite: HSG/Fächer/Informatik/Material

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.

Würfel

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
  1. schreiben einen Universalwürfel, bei dem man die Anzahl der Seiten festlegen kann.

  2. oder wir
  3. 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
Prototyp
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:

Wichtiges Bild

* 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'.