Home » Object Pascal » Klassen und Objekte

Klassen und Objekte

Was ist Objektorientierung?

Die objektorientierte Programmierung wurde in den 80er-Jahren entwickelt und soll die unstrukturierte Programmierung (am Anfang ein begin, dann Befehl auf Befehl und am Ende ein end, dazwischen möglichst viele Sprünge mit goto) ablösen. Die Objektorientierung ist ein sehr komplexes Gebiet und soll hier deshalb nur in einfachen Worten für das grundlegende Verständnis geschildert werden.
Wie der Name bereits sagt, sind Objekte zentrale Elemente der objektorientierten Programmierung. Ein Objekt ist eine Einheit mit einem Zustand und einem Verhalten, was gleich an einem Beispiel verdeutlicht wird. Zuvor müssen jedoch noch ein paar Begriffe geklärt werden.

Klassen

Klassen stellen den „Bauplan“ eines Objekts dar. Sie definieren, welche Eigenschaften/Attribute und welche Methoden ein Objekt besitzt und wie es auf bestimmte Ereignisse reagiert. Klassennamen beginnen in Delphi üblicherweise mit einem großen T (Type). Dabei handelt es sich um eine Vereinbarung, nicht um eine Regel. Soll Programmcode weitergegeben werden, so sollte man sich an diesen Quasi-Standard halten.
Statt „Klasse“ werden gelegentlich auch die Begriffe „Klassentyp“, „Objektklasse“ oder „Objekttyp“ verwendet.
In Delphi wird eine Klasse so deklariert:

type
  TAuto = class
  private
    FFarbe: String;
    FBaujahr: Integer;
    FTankinhalt: Integer;
    procedure SetFarbe(Farbe: String);
  public
    procedure Tanken(Liter: Integer);
    property Farbe: string read FFarbe write SetFarbe;
  end;

Eine solche Klassendefinition kann überall dort stehen, wo auch Typ-Deklarationen vorgenommen werden können. Pro Unit können natürlich auch mehrere Klassen deklariert werden. In Units, zu denen ein Fenster gehört, ist bereits die Klasse TForm1 zu finden. An diese kann man z.B. eigene Klassen anschließen:

type
  TForm1 = class(TForm)
      Button1: TButton;
      procedure Button1Click(Sender: TObject);
    private
    public
  end;  //Ende der Klasse TForm1
  TAuto = class
  private
    FFarbe: String;
    FBaujahr: Integer;
    FTankinhalt: Integer;
    procedure SetFarbe(Farbe: String);
  public
    procedure Tanken(Liter: Integer);
    property Farbe: string read FFarbe write SetFarbe;
  end; //Ende der Klasse TAuto

Die Klasse TAuto stellt nun den Bauplan für beliebig viele Instanzen dar. FFarbe, FBaujahr und FTankinhalt sind Attribute, also Eigenschaften eines TAutos. Außerdem kann eine Klasse Funkionen und Prozeduren enthalten, die etwas mit der Instanz machen, z.B. über das Tanken den Tankinhalt verändern.
Optimal ist es, wenn eine Klasse sich an tatsächlichen Dingen orientiert wie hier an einem Auto.

Instanzen/Objekte

Instanzen sind nun „lebendige“ Objekte, die nach dem Bauplan einer Klasse erstellt wurden. Sie belegen bei der Programmausführung Arbeitsspeicher und können Daten aufnehmen. Von jeder Klasse kann es beliebig viele Instanzen geben, die alle gleich aufgebaut sind, aber unterschiedliche Daten enthalten können.
Der Begriff „Objekt“ wird unterschiedlich verwendet. Manche gebrauchen ihn gleichbedeutend mit „Instanz“, andere verwenden ihn als Oberbegriff für Klassen und Instanzen. Beim Auftauchen dieses Begriffs muss also mit allem gerechnet werden. Auf diesen Seiten wird „Objekt“ gleichbedeutend mit „Instanz“ verwendet.
Um nun eine Instanz von oben beschriebener Klasse TAuto zu erstellen, muss zunächst eine Variable deklariert werden:

var
  MeinAuto: TAuto;

Variablen, deren Typ eine Klasse ist (wie oben „MeinAuto“) heißen Objektreferenz. Die Werte von Objektreferenzen sind Zeigerwerte (Adressen im Hauptspeicher). Das Deklarieren einer Objektreferenz wie oben reicht jedoch nicht aus, um eine Instanz zu erzeugen. Denn durch die reine Deklaration enthält MeinAuto nun den Wert nil. Es ist also noch kein Bereich im Hauptspeicher für unsere Autoinstanz reserviert worden. Wir haben lediglich mit dem Compiler vereinbart, dass es sich um etwas, das dem Bauplan von TAuto entspricht, handelt, wenn wir die Variable MeinAuto verwenden.
Die Erzeugung der Instanz geschieht über den Aufruf des Konstruktors „create“:

  MeinAuto := TAuto.Create;

Methoden

Methode ist der Überbegriff für Prozeduren und Funktionen, die Teil einer Klasse sind. Die Methoden stellen das Verhalten eines Objekts dar.

Methoden werden mit ihrem Kopf innerhalb der Klasse deklariert. An einer späteren Stelle im Code folgt die Implementierung der Methode. Diese erfolgt wie bei einer normalen Prozedur oder Funktion, außer dass vor den Methodennamen der Name der Klasse – getrennt durch einen Punkt – geschrieben wird.
Deklaration (im interface- oder implementation-Abschnitt einer Unit):

type
  TAuto = class
  private
    FFarbe: String;
    FBaujahr: Integer;
    FTankinhalt: Integer;
    procedure SetFarbe(Farbe: String);
  public
    procedure Tanken(Liter: Integer);
    property Farbe: string read FFarbe write SetFarbe;
  end;

Implementierung (im implementation-Abschnitt einer Unit):

Procedure TAuto.SetFarbe(Farbe: String);
Begin
  ...
End;

Procedure TAuto.Tanken(Liter: Integer);
Begin
  ...
End;

Der Aufruf erfolgt dann am Namen einer Instanz der Klasse (also nach dem Aufruf von Create):

var auto: TAuto;
...
auto := TAuto.Create;
auto.Tanken(40);

Self

Von einer Klasse können mehrere Instanzen gleichzeitig existieren. Innerhalb einer Methode kann über den Bezeichner Self auf die aufrufende Instanz zugegriffen werden. Beim Aufruf von weiteren Methoden derselben Klasse oder beim Zugriff auf Felder, kann Self auch weggelassen werden. Self steht innerhalb einer Methode also für das Objekt selbst.

procedure TAuto.Tanken(Liter: Integer);
begin
  FTankInhalt := Liter;
  self.FTankInhalt := Liter; //gleichbedeutend

In Klassenmethoden enthält Self die Klasse. Über Self.Create kann man deshalb eine Instanz der Klasse erzeugen.

Sender

Wird über den Objektinspektor einer Komponente ein Ereignis zugeordnet, wird automatisch das Gerüst einer Ereignisbehandlungsmethode erzeugt. Für das OnClick-Ereignis von Button1 auf Form1 zum Beispiel:

procedure TForm1.Button1Click(Sender: TObject);
begin

end;

Self steht hier für Form1, da Button1Click eine Methode von TForm1 ist. Und TForm1 hat nur die eine Instanz Form1. Will man dagegen wissen, welche Komponente das Ereignis ausgelöst hat – es könnten ja mehrere Buttons mit dieser Ereignisbehandlungsmethode verknüpft sein -, verwendet man den Parameter Sender:

if Sender = Button1 then ...

Klassenmethoden

Methoden werden normalerweise an einer Instanz (einem Objekt) aufgerufen. Bei Klassenmethoden ist das anders. Wie der Name bereits sagt, werden diese an einer Klasse aufgerufen. Definiert werden Klassenmethoden wie normale Methoden, jedoch mit dem Zusatz „class“:

type
  TKlasse = class(TObject)
  public
    class function GetInfo: string;
  end;

implementation

class function TKlasse.GetInfo: string;
begin
...
end;

Aus Klassenmethoden heraus kann nicht auf Felder der Klasse zugegriffen werden, sondern nur auf globale Variablen.

Beispiel: Eine Klassenmethode „simuliert“ eine Klassenvariable und zählt, wie viele Objekte es von einer Klasse gibt:

Unit Flaeche;

Interface

Type
  TFlaeche = class(TObject)
  private
  protected
    Function BerechneFlaeche: Double; virtual; abstract;
    Function BerechneUmfang: Double; virtual; abstract;
  public
    Constructor Create; virtual;
    Destructor Destroy; override;
    Class function count: Integer;
    Property Flaeche: Double read BerechneFlaeche;
    Property Umfang: Double read BerechneUmfang;
  End;

Implementation

Var anzahl: Integer;

Constructor TFlaeche.Create;
Begin
  Inc(anzahl);
End;

Destructor TFlaeche.Destroy;
Begin
  Dec(anzahl);
  Inherited;
End;

Class Function TFlaeche.Count: Integer;
Begin
  result := anzahl;
End;

End.

Im Konstruktor (Create) wird die Anzahl erhöht, im Destruktor (Destroy) wieder herabgesetzt. Über die Klassenmethode Count (und natürlich über die globale Variable) kann nun festgestellt werden, wie viele Instanzen es von TFlaeche gibt.

Botschaftsmethoden

Um auf Botschaften (eigene oder die vom Betriebssystem) zu reagieren, werden Botschaftsmethoden angelegt. Eine Botschaftsmethode hat immer einen einzigen var-Parameter. In Deklaration steht dahinter die Direktive message und die ID der Botschaft. Folgende Methode reagiert auf die Windows-Botschaft WM_CHAR:

Type
  TTextBox = class(TCustomControl)
  private
    Procedure WMChar(var Message: TWMChar); Message WM_CHAR;
  End;

Attribute oder Felder

Attribute sind mehr oder weniger Variablen, die zu einem Objekt gehören und damit seinen Zustand beschreiben. Attributnamen beginnen in Delphi mit einem großen F (Field). Wie auch beim T vor Klassennamen handelt es sich hierbei um eine Vereinbarung.

Es gehört zum „guten Ton“, Felder immer im private-Teil einer Klasse zu deklarieren, weil sie den internen Zustand eines Objekts enthalten, der von außen nur über definierte Schnittstellen (Methoden) verändert werden können sollte. Prinzipiell ist aber auch eine andere Position innerhalb der Klasse möglich. Jedoch müssen die Felddeklarationen vor den Methodendeklarationen stehen.
Beispiel:

type
 TAuto = class
 private
 FFarbe: string;
 FBaujahr: integer;
 procedure SetFarbe(Farbe: string);
 public
 property Farbe: string read FFarbe write SetFarbe;
 end;

Nach der Vereinbarung im Object Pascal-Styleguide beginnen alle Feldnamen mit einem großen F. Vom Compiler wird das allerdings nicht erzwungen. Es dient nur der Übersichtlichkeit.

Klassenfelder

Ab Delphi 2006

Klassenfelder gibt es seit Delphi 2006 und davor schon in den .NET-Versionen. Sie sind nicht über einen Instanznamen, sondern über den Namen der Klasse ansprechbar und gelten deshalb für alle Instanzen dieser Klasse. Deklariert werden Klassenfelder über class var:

type
  TAuto = class
  public
    class var
      Eigenschaft: Integer;

Eigenschaften oder Properties

Eigenschaften sind keine eigenständigen Variablen, sie belegen zur Laufzeit keinen Speicherplatz. Über sie lassen sich Lese- und Schreibzugriffe auf Attribute regeln. Die Eigenschaften sind es auch, die im Objektinspektor angezeigt werden.

Eigenschaften werden sinnvollerweise im public- oder published-Abschnitt einer Klasse definiert:

type TAuto = class
 private
 FFarbe: string;
 FBaujahr: integer;
 procedure SetFarbe(Farbe: string);
 public
 property Farbe: string read FFarbe write SetFarbe;
end;

Eine Eigenschaft verfügt über eine read- oder eine write-Angabe oder über beide. Dahinter folgt dann entweder direkt der Name des Feldes, auf das lesend oder schreibend zugegriffen werden soll, oder der Name einer Methode, die den Zugriff steuern soll.
Wird für read eine Methode verwendet, darf diese keinen Parameter haben und muss einen Rückgabewert liefern, der dem Typ der Eigenschaft entspricht. Bei einem Feld muss dieses natürlich auch den Typ der Eigenschaft haben.
Eine Methode für write muss genau einen Aufrufparameter besitzen, der ebenfalls dem Typ der Eigenschaft entspricht. Hinter read und write selbst wird jedoch immer nur der reine Methodenname ohne Parameter oder Rückgabewert angegeben.
Beispiel:

property Farbe: string read GetFarbe write SetFarbe;
...
function TAuto.GetFarbe: string;
procedure TAuto.SetFarbe(wert: string);

Enthält eine Eigenschaft nur eine read-Angabe, kann sie nur gelesen werden; enthält sie nur eine write-Angabe, kann sie nur geschrieben werden. Im jeweils anderen Fall tritt ein Fehler auf.
Properties können nicht als var-Parameter eingesetzt werden, da es sich bei ihnen ja nicht um direkte Zugriffe auf Felder handeln muss; sondern es können ja „nur“ Methoden dahinter stecken.

Array-Eigenschaften

Eigenschaften können auch wie Arrays arbeiten. Die Syntax sieht in einem solchen Fall wie folgt aus:

property Adressen[index: integer]: string read GetAdresse write SetAdresse;
...
function TKlasse.GetAdresse(index: integer): string;
procedure TKlasse.SetAdresse(index: integer; wert: string);

Klasseneigenschaften

Ab Delphi 2006

In Delphi ab 2006 gibt es sog. Klasseneigenschaften (Class Properties). Diese Eigenschaften sind statisch und werden nicht an einer Instanz verwendet, sondern gelten für die Klasse. Deklariert werden sie im class var-Abschnitt einer Klassendefinition. Zu beachten ist, dass hier zum Lesen und Schreiben nur Klassenfelder und/oder Klassenmethoden verwendet werden können!

type
  TAuto = class
  private
    class var FMyVar: String;
  public
    class procedure SetMyVar(Value: String); static;
    class property MyVar: String read FMyVar write SetMyVar;