Programmierkurs: Delphi: Pascal: Klassen
Aus Wikibooks
Inhaltsverzeichnis |
[Bearbeiten] Klassen
[Bearbeiten] Einleitung
Die Grundlage für die objektorientierte Programmierung, kurz OOP, bilden Klassen. Diese kapseln, ähnlich wie Records, verschiedene Daten zu einer Struktur. Gleichzeitig liefern sie Methoden mit, welche die vorhandenen Daten bearbeiten.
Der Vorteil von Klassen besteht also darin, dass man mit ihnen zusammengehörige Variablen, Funktionen und Prozeduren zusammenfassen kann. Weiterhin können - bei entsprechender Implementation - die Daten einer Klasse nicht „von außen“ geändert werden.
[Bearbeiten] Aufbau einer Klasse
[Bearbeiten] Allgemeiner Aufbau
Die einfachste Klasse entspricht in ihrem Aufbau einem Record:
program Klassentest; type TMyRec = record EinByte: Byte; EinString: string; end; TMyClass = class FEinByte: Byte; FEinString: string; end; var MyRec: TMyRec; MyClass: TMyClass; begin MyRec.EinByte := 15; MyClass := TMyClass.Create; MyClass.FEinString := 'Hallo Welt!'; MyClass.Free; // bzw. MyClass.Destroy; end.
Hierbei kann man bereits einen Unterschied zu Records erkennen: Während man jederzeit auf die Daten eines Records zugreifen kann, muss bei einer Klasse zunächst Speicher angefordert und zum Schluss wieder freigeben werden. Die speziellen Methoden Create und Destroy bzw. Free sind in jeder Klasse enthalten und müssen nicht gesondert programmiert werden. Zur Freigabe des Speichers sollte dabei Free bevorzugt verwendet werden. Hierzu mehr im Kapitel Konstruktoren und Destruktoren.
Weiterhin hat es sich durchgesetzt, die Variablen einer Klasse, Felder genannt, immer mit dem Buchstaben F zu beginnen. So werden Verwechselungen mit globalen und lokalen Variablen vermieden.
[Bearbeiten] Sichtbarkeit der Daten
Im oben gezeigten Beispiel kann auf die Felder wie bei einem Record zugegriffen werden. Dies sollte man unter allen Umständen vermeiden!
Hierfür gibt es eine ganze Reihe von Möglichkeiten. Zunächst einmal können Felder, sowie Methoden und Eigenschaften einer Klasse nach außen hin „versteckt“ werden. Das erreicht man mit folgenden Schlüsselwörtern:
strict private- auf diese Daten kann außerhalb der Klasse nicht zugegriffen werdenprivate- wiestrict private, allerdings kann auch in der beinhaltenden Unit darauf zugegriffen werdenstrict protected- hierauf kann man nur innerhalb der Klasse und ihrer Nachfahren zugreifenprotected- wiestrict protected, allerdings kann auch in der beinhaltenden Unit darauf zugegriffen werdenpublic- diese Daten sind uneingeschränkt zugänglichpublished- zusätzlich zu public können diese Daten auch im Objekt-Inspektor von Delphi™ bearbeitet werden (nur bei Eigenschaften von Komponenten sinnvoll).
Das entsprechende Schlüsselwort wird der Gruppe von Daten vorangestellt, für die diese Sichtbarkeit gelten soll. Um die Felder unserer Klasse zu verstecken, schreiben wir also:
type TMyClass = class private FEinByte: Byte; FEinString: string; end;
Wenn man jetzt versucht, wie oben gezeigt, einem Feld einen Wert zuzuweisen oder ihn auszulesen, wird bereits bei der Kompilierung eine Fehlermeldung ausgegeben.
[Bearbeiten] Methoden
Wie erhält man nun aber Zugriff auf die Daten? Dies erreicht man über öffentlich zugängliche Methoden, mit denen die Daten ausgelesen und geändert werden können.
Eine Methode ist eine fest mit der Klasse verbundene Funktion oder Prozedur. Daher wird sie auch wie Felder direkt innerhalb der Klasse definiert:
TMyClass = class public function GetString: string; procedure SetString(NewStr: string); end;
Die Ausführung der Methoden erfolgt direkt über die Variable dieses Klassentyps:
var MyClass: TMyClass; ... MyClass.SetString('Hallo Welt!'); WriteLn(MyClass.GetString);
In der Typdefinition werden nur der Name und die Parameter von Methoden definiert. Die Implementation, also die Umsetzung dessen, was eine Methode tun soll, erfolgt außerhalb der Klasse, genau wie bei globalen Funktionen und Prozeduren. Allerdings muss der Klassenname vorangestellt werden:
function TMyClass.GetString: string; begin Result := FEinString; end; procedure TMyClass.SetString(NewStr: string); begin FEinString := NewStr; end;
Da die Methoden GetString und SetString Mitglieder der Klasse sind, können diese auf das private Feld FEinString zugreifen.
Ebenso wie globale Funktionen und Prozeduren lassen sich auch Methoden überladen. Dies bedeutet, dass mehrere Prozeduren mit dem gleichen Namen aber unterschiedlichen Parametern innerhalb einer Klasse deklariert werden. Hierzu ein vollständiges Programm als Beispiel:
program Ueberladen; {$APPTYPE CONSOLE} uses SysUtils; type TTestKlasse = class public function ZaehleStellen(zahl: Cardinal): Integer; overload; function ZaehleStellen(wort: string): Integer; overload; end; function TTestKlasse.ZaehleStellen(zahl: Cardinal): Integer; begin Result := Length(IntToStr(zahl)); end; function TTestKlasse.ZaehleStellen(wort: string): Integer; begin Result := Length(wort); end; var Zaehler: TTestKlasse; begin Zaehler := TTestKlasse.Create; Writeln(Zaehler.ZaehleStellen(16384)); Writeln(Zaehler.ZaehleStellen('Donnerstag')); Readln; Zaehler.Free; end.
Im ersten Fall – Writeln(ZaehleStellen(16384)); – wird die Methode TTestKlasse.ZaehleStellen(zahl: Cardinal): Integer aufgerufen, da der Übergabeparameter vom Typ Cardinal ist. Es wird 5 ausgegeben.
Im zweiten Fall – Writeln(ZaehleStellen('Donnerstag')); – wird die Methode TTestKlasse.ZaehleStellen(wort: string): Integer aufgerufen, da der Übergabeparameter ein String ist. Dementsprechend wird der Wert 10 ausgegeben.
In beiden Fällen wird die Stellenanzahl mittels Length bestimmt. Da Length aber eine Zeichenkette erwartet, wird der Zahlwert im ersten Fall zunächst in eine Zeichenkette umgewandelt und dann die Länge dieser Zeichenkette bestimmt.
Näheres zum Überladen unter Prozeduren und Funktionen.
[Bearbeiten] Eigenschaften
Für unser Beispiel erscheint der Zugriff etwas umständlich. Daher gibt es noch eine andere einfache Möglichkeit, Daten einer Klasse auszulesen und zu ändern: Eigenschaften (engl. properties).
Eigenschaften lassen sich wie Variablen behandeln, das heißt, man kann sie (wenn gewünscht) auslesen oder (wenn gewünscht) ändern. Die interne Umsetzung bleibt dabei verborgen. So kann eine Eigenschaft zum Beispiel direkt auf ein Feld oder über den Umweg einer Methode darauf zugreifen:
type TMyClass = class private FEinInt: Integer; function HoleDoppelteZahl: Integer; procedure SpeichereHalbeZahl(DoppZahl: Integer); // Name des Parameters ist egal public property Zahl: Integer read FEinInt write FEinInt; property DoppelZahl: Integer read HoleDoppelteZahl write SpeichereHalbeZahl; end;
Sowohl das Feld als auch die Methoden sind versteckt. Die einzige Verbindung zur Außenwelt besteht über die Eigenschaften. Hier greift die Eigenschaft „Zahl“ beim Lesen und Schreiben direkt auf das Feld zu, während „DoppelZahl“ in beiden Fällen auf Methoden zurückgreift. Da DoppelZahl immer das Doppelte von Zahl sein soll (so sagt es zumindest der Name), werden die Methoden wie folgt implementiert:
function HoleDoppelteZahl: Integer; begin Result := FEinInt * 2; end; procedure SpeichereHalbeZahl(DoppZahl: Integer); begin FEinInt := DoppZahl div 2; end;
Diese Klasse führt Berechnungen durch, ohne dass es der Benutzer mitbekommt. Weist man Zahl einen Wert zu, erhält man aus DoppelZahl automatisch das Doppelte. Andersherum kann man DoppelZahl einen Wert zuweisen und erhält mit Zahl die Hälfte. Von außen betrachtet erscheinen beide Eigenschaften jedoch wie Felder:
with MyClass do begin Zahl := 4; WriteLn(DoppelZahl); // Schreibt eine 8 DoppelZahl := 20; WriteLn(Zahl); // ergibt 10 und nicht etwa 4 end;
Soll DoppelZahl einen Schreibschutz erhalten, lässt man in der Definition den Teil „write ...“ weg. Damit kann auch die entsprechende Methode SpeichereHalbeZahl entfernt werden.
Die verschiedenen Arten von Eigenschaften werden ausführlicher in einem eigenen Kapitel behandelt.
| Inhaltsverzeichnis | Pascal: Konstruktoren und Destruktoren |