Klassen und Objekte (Delphi)

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Klassen und Objekte - Index

In diesem Thema wird Folgendes behandelt:

  • Deklarationssyntax von Klassen
  • Vererbung und Gültigkeitsbereich
  • Sichtbarkeit von Klassenelementen
  • Vorwärtsdeklarationen und voneinander abhängige Klassen

Klassentypen

Eine Klasse (oder ein Klassentyp) definiert eine Struktur von Feldern, Methoden und Eigenschaften. Die Instanzen eines Klassentyps heißen Objekte. Die Felder, Methoden und Eigenschaften einer Klasse nennt man ihre Komponenten oder Elemente (bzw. Member).

  • Felder sind im Wesentlichen Variablen, die zu einem Objekt gehören. Sie definieren, wie Felder eines Datensatzes, Datenelemente, die in jeder Instanz der Klasse vorhanden sind.
  • Eine Methode ist eine Prozedur oder Funktion, die zu einer bestimmten Klasse gehört. Die meisten Methoden führen Operationen mit Objekten (Instanzen einer Klasse) durch. Manche Methoden arbeiten jedoch mit den Klassentypen selbst. Sie werden als Klassenmethoden bezeichnet.
  • Eine Eigenschaft ist eine Schnittstelle zu den Daten eines Objekts (die oftmals in einem Feld gespeichert sind). Eigenschaften verfügen über Zugriffsspezifizierer, die bestimmen, wie ihre Daten gelesen und geändert werden. Sie erscheinen für die anderen Bestandteile eines Programms (außerhalb des Objekts) in vielerlei Hinsicht wie ein Feld.

Objekte sind dynamisch zugewiesene Speicherblöcke, deren Struktur durch ihren Klassentyp festgelegt wird. Jedes Objekt verfügt über eine eigene Kopie der in der Klasse definierten Felder. Die Methoden werden jedoch von allen Instanzen gemeinsam genutzt. Das Erstellen und Freigeben von Objekten erfolgt mithilfe spezieller Methoden, den Konstruktoren und Destruktoren.

Eine Klassentypvariable ist eigentlich ein Zeiger auf ein Objekt. Aus diesem Grund können auch mehrere Variablen auf dasselbe Objekt verweisen. Klassentypvariablen können wie andere Zeiger den Wert nil aufnehmen. Sie müssen aber nicht explizit dereferenziert werden, um auf das betreffende Objekt zuzugreifen. So wird beispielsweise durch die Anweisung SomeObject.Size := 100 der Eigenschaft Size des Objekts, auf das von SomeObject verwiesen wird, der Wert 100 zugewiesen. In diesem Fall brauchen Sie nicht SomeObject^.Size := 100 anzugeben.

Ein Klassentyp muss deklariert und benannt werden, damit er instantiiert werden kann (er kann also nicht in einer Variablendeklaration definiert werden). Deklarieren Sie Klassen nur im äußersten Gültigkeitsbereich eines Programms oder einer Unit, nicht in einer Prozedur oder Funktion.

Eine Klassentypdeklaration hat die folgende Form:

 type
    className = class [abstract | sealed] (ancestorClass)
        type
          nestedTypeDeclaration
        const
	  nestedConstDeclaration
        memberList
    end;

Erforderliche Elemente der Klassentypdeklaration

  • className ist ein beliebiger gültiger Bezeichner.
  • memberList deklariert die Member der Klasse: Felder, Eigenschaften und Methoden.

Optionale Elemente der Klassentypdeklaration

  • abstract. Eine gesamte Klasse kann als "abstract" deklariert werden, auch wenn sie keine abstrakten virtuellen Methoden enthält.
  • sealed. Eine "sealed"-Klasse kann nicht durch Vererbung erweitert werden.
  • ancestorClass. Wenn Sie (ancestorClass) weglassen, erbt die neue Klasse direkt von der vordefinierten Klasse System.TObject. Wenn Sie eine (ancestorClass) angeben und "memberList" leer ist, brauchen Sie "end" nicht anzugeben.
  • nestedTypeDeclaration. Verschachtelte Typen ermöglichen, eng miteinander verwandte Typen in Gruppen zusammenzufassen und Namenskollisionen zu vermeiden.
  • nestedConstDeclaration. Verschachtelte Konstanten ermöglichen, eng miteinander verwandte Typen in Gruppen zusammenzufassen und Namenskollisionen zu vermeiden.

Eine Klasse kann nicht gleichzeitig abstract und sealed sein. Mit der Syntax [abstract | sealed] (den eckigen Klammern [ ] und dem Pipe-Zeichen | dazwischen) wird festgelegt, dass nur eines der optionalen Schlüsselwörter sealed oder abstract verwendet werden kann. Nur die Schlüsselwörter sealed und abstract sind von Bedeutung. Die eckigen Klammern und das Pipe-Symbol sollten gelöscht werden.

Hinweis: Delphi lässt die Instantiierung einer als abstract deklarierten Klasse aus Gründen der Abwärtskompatibilität zu, aber dieses Feature sollten nicht mehr verwendet werden.

Methoden werden in einer Klassendeklaration als Funktions- oder Prozedurköpfe ohne Rumpf angegeben. Die definierenden Deklarationen folgen dann an einer anderen Stelle im Programm.

Das folgende Beispiel zeigt die Deklaration der Klasse TMemoryStream in der Unit Classes:

TMemoryStream = class(TCustomMemoryStream)
  private
    FCapacity: Longint;
    procedure SetCapacity(NewCapacity: Longint);
  protected
    function Realloc(var NewCapacity: Longint): Pointer; virtual;
    property Capacity: Longint read FCapacity write SetCapacity;
  public
    destructor Destroy; override;
    procedure Clear;
    procedure LoadFromStream(Stream: TStream);
    procedure LoadFromFile(const FileName: string);
    procedure SetSize(const NewSize: Int64); override;
    procedure SetSize(NewSize: Longint); override;
    function Write(const Buffer; Count: Longint): Longint; override;
    function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override;
  end; // deprecated 'Use TBytesStream';

Classes.TMemoryStream ist von Classes.TCustomMemoryStream abgeleitet und erbt die meisten Elemente (Member). Zusätzlich werden jedoch mehrere Methoden und Eigenschaften einschließlich des Destruktors Destroy definiert bzw. neu definiert. Der Konstruktor Create wird ohne Änderung von System.TObject übernommen und daher nicht neu deklariert. Jedes Element (Member) wird als private, protected oder public deklariert (diese Klasse hat keine published-Elemente). Informationen zu diesen Begriffen finden Sie weiter unten.

Ausgehend von dieser Deklaration kann eine Instanz von TMemoryStream folgendermaßen erstellt werden:

var
  stream: TMemoryStream;

begin
  stream := TMemoryStream.Create;

Vererbung und Gültigkeitsbereich

Beim Deklarieren einer Klasse kann der direkte Vorfahr angegeben werden. Zum Beispiel:

  type TSomeControl = class(TControl);

Hier wird die Klasse TSomeControl deklariert, die von Vcl.Controls.TControl abgeleitet ist. Ein Klassentyp erbt automatisch alle Elemente (Member) seines direkten Vorfahren. Außerdem können jederzeit neue Elemente erstellt oder geerbte Elemente neu definiert werden. Es ist aber nicht möglich, Elemente zu entfernen, die in einer Vorfahrklasse definiert wurden. Aus diesem Grund enthält TSomeControl alle in der Klasse Vcl.Controls.TControl definierten Elemente und jeden der Vcl.Controls.TControl--Vorfahren.

Der Gültigkeitsbereich eines Elementbezeichners reicht von seiner Deklaration bis zum Ende der Klassendeklaration und erstreckt sich über alle Nachkommen des Klassentyps und alle in der Klasse und ihren Nachfahren definierten Methoden.

TObject und TClass

Die in der Unit System deklarierte Klasse System.TObject ist der absolute Vorfahr aller anderen Klassentypen. In der Klasse System.TObject sind nur einige wenige Methoden definiert, zu denen ein einfacher Konstruktor und ein Destruktor gehören. Außer System.TObject ist in der Unit System auch noch der Klassenreferenztyp System.TClass deklariert:

  TClass = class of TObject;

Wenn Sie in der Deklaration eines Klassentyps keinen Vorfahren angegeben, erbt die Klasse direkt von System.TObject. Die Anweisung:

type TMyClass = class
      ...
     end;

ist gleichbedeutend mit:

type TMyClass = class(TObject)
      ...
     end;

Die zweite Variante sollte jedoch aus Gründen der Lesbarkeit bevorzugt werden.

Kompatibilitätsregeln für Klassentypen

Ein Klassentyp ist mit jedem seiner Vorfahren zuweisungskompatibel. Eine Klassentypvariable kann daher auf eine Instanz eines beliebigen untergeordneten Typs verweisen. Ausgehend von den Deklarationen:

type
  TFigure = class(TObject);
  TRectangle = class(TFigure);
  TSquare = class(TRectangle);
var
 Fig: TFigure;

können der Variable Fig Instanzen von TFigure, TRectangle und TSquare zugewiesen werden.

Objekttypen

Der Delphi-Compiler unterstützt als Alternative zu Klassentypen die Deklaration von Objekttypen. Die Syntax hierfür lautet:

type objectTypeName = object (ancestorObjectType)
        memberList
     end;

objectTypeName ist ein beliebiger gültiger Bezeichner, (ancestorObjectType) ist optional, und memberList definiert die Felder, Methoden und Eigenschaften der Klasse. Wird (ancestorObjectType) nicht angegeben, hat der neue Typ keinen Vorfahren. Bei Objekttypen können Elemente (Member) nicht als published deklariert werden.

Da Objekttypen nicht von System.TObject abgeleitet sind, verfügen sie über keine integrierten Konstruktoren, Destruktoren oder andere Methoden. Instanzen können mit der Prozedur New erstellt und mit Dispose freigegeben werden. Sie können Variablen eines Objekttyps aber auch einfach wie bei einem Record-Typ deklarieren.

Objekttypen werden nur aus Gründen der Abwärtskompatibilität unterstützt. Ihre Verwendung ist nicht zu empfehlen.

Sichtbarkeit von Klassenelementen

In einer Klasse hat jedes Element (Member) ein Sichtbarkeitsattribut, das durch die reservierten Wörter private, protected, public, published oder automated angegeben wird. Zum Beispiel:

  published property Color: TColor read GetColor write SetColor;

In diesem Beispiel wird die Eigenschaft Color als published deklariert. Die Sichtbarkeit bestimmt, wo und wie auf ein Element zugegriffen werden kann. private entspricht der geringsten, protected einer mittleren und public, published und automated der größten Sichtbarkeit.

Ein Element ohne Attribut erhält automatisch die Sichtbarkeit des vorhergehenden Elements in der Deklaration. Die Elemente am Anfang einer Klassendeklaration ohne explizite Sichtbarkeitsangabe werden standardmäßig als published deklariert, wenn die Klasse im Status {$M+} compiliert oder von einer mit {$M+} compilierten Klasse abgeleitet wurde. Andernfalls erhalten sie das Attribut public.

Aus Gründen der Lesbarkeit sollten Sie die Elemente einer Klassendeklaration nach ihrer Sichtbarkeit gruppieren. Nehmen Sie daher zuerst alle private-Elemente, anschließend alle protected-Elemente usw. in die Deklaration auf. Bei dieser Vorgehensweise braucht das Sichtbarkeitsattribut nur einmal angegeben zu werden, und es markiert immer den Anfang eines neuen Deklarationsabschnitts. Eine typische Klassendeklaration sieht somit folgendermaßen aus:

type
  TMyClass = class(TControl)
    private
      { private declarations here }
    protected
      { protected declarations here }
    public
      { public declarations here }
    published
      { published declarations here }
  end;

Sie können die Sichtbarkeit einer Eigenschaft in einer untergeordneten Klasse durch Neudeklarieren erhöhen, jedoch nicht verringern. So kann beispielsweise eine protected-Eigenschaft in einer abgeleiteten Klasse als public deklariert werden, nicht aber als private. Zudem können published-Eigenschaften in abgeleiteten Klassen nicht als public neu deklariert werden. Weitere Informationen hierzu finden Sie unter Eigenschaften überschreiben und neu deklarieren.

private-, protected- und public-Elemente

Auf ein private-Element (Member) kann nur innerhalb des Moduls (Unit oder Programm) zugegriffen werden, in dem die Klasse deklariert ist. Eine private-Methode kann also nicht von anderen Modulen aufgerufen werden, und als private deklarierte Felder oder Eigenschaften können nicht von anderen Modulen gelesen oder geschrieben werden. Indem Sie verwandte Klassendeklarationen in dasselbe Modul aufnehmen, können Sie jeder Klasse den Zugriff auf alle private-Elemente einer anderen Klasse ermöglichen, ohne die Elemente anderen Modulen bekannt zu machen. Damit ein Element nur innerhalb seiner Klasse sichtbar ist, muss es als strict private deklariert werden.

Ein protected-Element ist innerhalb des Moduls mit der Klassendeklaration und in allen abgeleiteten Klassen (unabhängig davon, in welchem Modul sie deklariert sind) sichtbar. Eine protected-Methode kann aufgerufen und auf ein protected-Feld bzw. eine protected-Eigenschaft kann von allen Methoden einer Klasse zugriffen werden, die von der Klasse mit der protected-Deklaration abgeleitet ist. Mit diesem Sichtbarkeitsattribut werden also Elemente deklariert, die nur in den Implementierungen abgeleiteter Klassen verwendet werden sollen.

Ein public-Element ist überall dort sichtbar, wo auf seine Klasse verwiesen werden kann.

Sichtbarkeitsbezeichner "strict"

Neben den Sichtbarkeitsbezeichnern private und protected unterstützt der Delphi-Compiler zusätzliche Sichtbarkeitseinstellungen mit größeren Zugriffsbeschränkungen: strict private und strict protected. Diese Einstellungen können in Win32-Anwendungen verwendet werden.

Auf Klassenelemente mit der Sichtbarkeit strict private kann nur in der Klasse zugegriffen werden, in der sie deklariert sind. Für Prozeduren und Funktionen in derselben Unit sind sie nicht sichtbar. Klassenelemente mit der Sichtbarkeit strict protected sind nicht nur in der Klasse sichtbar, in der sie deklariert sind, sondern auch in jeder abgeleiteten Klasse. Dabei spielt es keine Rolle, wo diese deklariert ist. Wenn Instanzelemente (solche, die ohne die Schlüsselwörter class oder class var deklariert sind) als strict private oder strict protected deklariert sind, kann auf sie nicht außerhalb der Instanz einer Klasse, in der sie erscheinen, zugegriffen werden. Eine Instanz einer Klasse kann nicht auf strict private oder strict protected-Instanzelemente in anderen Instanzen derselben Klasse zugreifen.

Hinweis: Das Wort strict wird im Kontext einer Klassendeklaration als Direktive betrachtet. Es ist nicht zulässig, das Wort strict innerhalb einer Klassendeklaration als Elementname zu verwenden. Außerhalb der Klassendeklaration kann es jedoch eingesetzt werden.

published-Elemente

published-Elemente haben dieselbe Sichtbarkeit wie public-Elemente. Im Unterschied zu diesen werden jedoch für published-Elemente Laufzeit-Typinformationen generiert. Sie ermöglichen einer Anwendung, die Felder und Eigenschaften eines Objekts dynamisch abzufragen und seine Methoden zu lokalisieren. Diese RTTI-Informationen werden verwendet, um beim Speichern und Laden von Formulardateien auf die Werte von Eigenschaften zuzugreifen, Eigenschaften im Objektinspektor anzuzeigen und spezielle Methoden (so genannte Ereignisbehandlungsroutinen) bestimmten Eigenschaften (den Ereignissen) zuzuordnen.

published-Eigenschaften sind auf bestimmte Datentypen beschränkt. Ordinal-, String-, Klassen-, Interface-, Variant- und Methodenzeigertypen können mit dieser Sichtbarkeit deklariert werden. Bei Mengentypen ist dies nur möglich, wenn die Ober- und Untergrenze des Basistyps einen Ordinalwert von 0 bis 31 hat. (Die Menge muss also in ein Byte, Word oder Double Word passen.) Auch alle Real-Typen außer Real48 können als published deklariert werden. Für Eigenschaften von Array-Typen ist published dagegen nicht zulässig (im Gegensatz zu den weiter unten beschriebenen Array-Eigenschaften).

Einige Eigenschaften, die als published deklariert werden können, werden vom Streaming-System nicht vollständig unterstützt. Dazu gehören die Eigenschaften von Record-Typen, die Array-Eigenschaft aller als published deklarierbaren Typen und die Eigenschaften von Aufzählungstypen, die anonyme Werte enthalten. Wenn Sie eine derartige Eigenschaft als published deklarieren, wird sie im Objektinspektor nicht korrekt angezeigt, und ihr Wert geht beim Schreiben des Streams auf das Speichermedium verloren.

Obwohl alle Methoden als published deklariert werden können, sind in einer Klasse nicht zwei oder mehr überladene published-Methoden mit demselben Namen erlaubt. Felder können nur mit dieser Sichtbarkeit angegeben werden, wenn sie einen Klassen- oder Interface-Typ haben.

Eine Klasse kann nur published-Elemente haben, wenn sie mit {$M+} compiliert wird oder von einer Klasse abgeleitet ist, die mit {$M+} compiliert wurde. Die meisten Klassen mit published-Elementen sind von der Klasse Classes.TPersistent abgeleitet, die im Status {$M+} compiliert wird. Die Anweisung $M wird daher nur äußerst selten benötigt.

Hinweis: Bezeichner, die Unicode-Zeichen enthalten, sind in published-Abschnitten von Klassen oder in von published-Elementen verwendeten Typen nicht zulässig.

automated-Elemente (nur Win32)

automated-Elemente haben dieselbe Sichtbarkeit wie public-Elemente. Im Gegensatz zu diesen werden aber für automated-Elemente die für Automatisierungsserver benötigten Automatisierungs-Typinformationen erzeugt. automated-Elemente werden normalerweise nur in Win32-Klassen eingesetzt. Das reservierte Wort automated ist aus Gründen der Abwärtskompatibilität vorhanden. In der Klasse TAutoObject der Unit ComObj wird automated nicht verwendet.

Für automated-Methoden und -Eigenschaften gelten folgende Einschränkungen.

  • Die Typen aller Eigenschaften, Array-Eigenschaftsparameter, Methodenparameter und Funktionsergebnisse müssen automatisierbar sein. automated-Typen sind Byte, Currency, Real, Double, Longint, Integer, Single, Smallint, AnsiString, WideString, TDateTime, Variant, OleVariant, WordBool und alle Interface-Typen.
  • Methodendeklarationen müssen die Standardaufrufkonvention register verwenden. Sie können virtuell sein, nicht aber dynamisch.
  • Eigenschaftsdeklarationen dürfen die Zugriffsbezeichner (read und write), aber keine anderen Bezeichner (index, stored, default und nodefault) enthalten. Zugriffsbezeichner müssen einen Methodenbezeichner angeben, der die Standardaufrufkonvention register verwendet. Feldbezeichner sind nicht zulässig.
  • Eigenschaftsdeklarationen müssen einen Typ angeben. Überschreiben ist hier nicht erlaubt.

Die Deklaration einer automated-Methode oder -Eigenschaft kann die Direktive dispid enthalten. Die Angabe einer bereits vergebenen ID in einer dispid-Direktive führt zu einem Fehler.

Unter Win32 muss auf diese Direktive eine Integer-Konstante folgen, die die Dispatch-ID für die Automatisierung des Elements angibt. Fehlt diese Angabe, weist der Compiler automatisch eine Dispatch-ID zu. Diese ID ist um 1 höher als die größte Dispatch-ID, die bereits von einer Methode oder Eigenschaft der Klasse bzw. ihrer Vorfahren verwendet wird. Weitere Informationen über die Automatisierung (nur Win32) finden Sie unter Automatisierungsobjekte.

Vorwärtsdeklarationen und voneinander abhängige Klassen

Wenn die Deklaration eines Klassentyps wie im folgenden Beispiel mit dem Wort class und einem Semikolon endet

type  className = class;

und nach dem Wort class kein Vorfahr und keine Elemente folgen, handelt es sich um eine Vorwärtsdeklaration. Eine Vorwärtsdeklaration muss durch eine definierende Deklaration dieser Klasse im selben Typdeklarationsabschnitt aufgelöst werden. Das bedeutet, dass zwischen einer Vorwärtsdeklaration und ihrer definierenden Deklaration ausschließlich andere Typdeklarationen stehen dürfen.

Vorwärtsdeklarationen ermöglichen voneinander abhängige Klassen. Zum Beispiel:

type
  TFigure = class;   // forward declaration
  TDrawing = class
    Figure: TFigure;
    // ...
  end;
 
  TFigure = class   // defining declaration
    Drawing: TDrawing;
    // ...
  end;

Verwechseln Sie Vorwärtsdeklarationen nicht mit vollständigen Deklarationen von Typen, die ohne Angabe von Elementen von System.TObject abgeleitet werden.

type
  TFirstClass = class;   // this is a forward declaration
  TSecondClass = class   // this is a complete class declaration
  end;                   //
  TThirdClass = class(TObject);  // this is a complete class declaration

Siehe auch