Programmierkurs: Delphi: Pascal: Klassen
Einleitung
BearbeitenDie 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.
Aufbau einer Klasse
BearbeitenAllgemeiner Aufbau
BearbeitenDie 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.
Sichtbarkeit der Daten
BearbeitenIm 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. Mit neueren Delphi Versionen gab es keinen Fehler, erst das Wort "strict" vor "private" löste o.a. Fehler aus.
Methoden
BearbeitenWie 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 private FEinString: string; 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.
Eigenschaften
BearbeitenUm den Zugriff auf die Daten einer Klasse zu vereinfachen und flexibler zu gestalten, gibt es noch eine andere Möglichkeit: 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 jedoch verborgen. So kann eine Eigenschaft direkt auf ein Feld oder mittels einer Methode die Daten der Klasse auswerten:
type TTestKlasse = class private FZugriffe: Integer; FText: string; procedure SetzeString(Neu: string); public property SchreibZugriffe: Integer read FZugriffe; // nur lesbar property Text: string read FText write SetzeString; // les- und schreibbar end;
Sowohl die Felder als auch die Methode sind versteckt. Die einzige Verbindung zur Außenwelt besteht über die Eigenschaften. Hier greift die Eigenschaft „SchreibZugriffe“ beim Lesen direkt auf das Feld zu und erlaubt keinen Schreibvorgang, während „Text“ zwar beides erlaubt, beim Schreiben aber auf eine Methode zurückgreift. Die Methode wird nun wie folgt implementiert:
procedure TTestKlasse.SetzeString(Neu: string); begin FText := Neu; Inc(FZugriffe); end;
Diese Klasse erhöht den Wert des Feldes FZugriffe, ohne dass es der Benutzer mitbekommt. Bei jedem Schreibzugriff auf die Eigenschaft Text wird FZugriffe um 1 erhöht. Dieser Wert kann mit der Eigenschaft SchreibZugriffe nur ausgelesen, aber nicht geändert werden. „Text“ erscheint daher nach außen hin eher wie eine Variable und „SchreibZugriffe“ eher wie eine Konstante. Hier ein kleines Beispiel mit einem Objekt dieser Klasse:
TestKlasse := TTestKlasse.Create; with TestKlasse do begin Text := 'Hallo!'; Writeln(SchreibZugriffe, ', ', Text); // gibt "1, Hallo!" aus Text := 'Und auf Wiedersehen!'; Writeln(SchreibZugriffe, ', ', Text); // gibt "2, Und auf Wiedersehen!" aus end; TestKlasse.Free;
Die verschiedenen Arten von Eigenschaften werden ausführlicher im Kapitel Eigenschaften behandelt.
Pascal: Threads | Inhaltsverzeichnis | Pascal: Konstruktoren und Destruktoren |