Programmierkurs: Delphi: Pascal: Klassen

Einleitung

Bearbeiten

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.

Aufbau einer Klasse

Bearbeiten

Allgemeiner Aufbau

Bearbeiten

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.

Sichtbarkeit der Daten

Bearbeiten

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 werden
  • private - wie strict private, allerdings kann auch in der beinhaltenden Unit darauf zugegriffen werden
  • strict protected - hierauf kann man nur innerhalb der Klasse und ihrer Nachfahren zugreifen
  • protected - wie strict protected, allerdings kann auch in der beinhaltenden Unit darauf zugegriffen werden
  • public - diese Daten sind uneingeschränkt zugänglich
  • published - 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

Bearbeiten

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
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

Bearbeiten

Um 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