Objektorientierte Programmierung in Delphi
Transcrição
Objektorientierte Programmierung in Delphi
Objektorientierte Programmierung in Delphi Spezialgebiet Informatik Manuel Pöter – 07.01.2003 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz Inhaltsverzeichnis Allgemeine Einführung __________________________________________ 1 1 Eigenschaften der objektorientierten Programmierung __________________1 2 Klassen___________________________________________________________1 3 2.1 Was sind Klassen? __________________________________________________________ 1 2.2 Eigenschaften von Klassen ____________________________________________________ 1 2.2.1 Kapselung_____________________________________________________________ 1 2.2.2 Vererbung_____________________________________________________________ 2 2.2.3 Polymorphie ___________________________________________________________ 2 Objekte __________________________________________________________2 3.1 Was ist ein Objekt? __________________________________________________________ 2 3.2 Attribute und Methoden ______________________________________________________ 3 Objekte in Delphi _______________________________________________ 4 1 Die Klassendeklaration _____________________________________________4 2 Aller Anfang ist TObject ____________________________________________4 3 Kapselung ________________________________________________________5 4 5 6 3.1 private ____________________________________________________________________ 5 3.2 3.3 public ____________________________________________________________________ 6 protected __________________________________________________________________ 6 3.4 published__________________________________________________________________ 6 Methoden_________________________________________________________6 4.1 Statische __________________________________________________________________ 7 4.2 Virtuelle __________________________________________________________________ 7 4.3 Dynamische _______________________________________________________________ 7 4.4 4.5 Botschaftsverarbeitende Methoden______________________________________________ 7 Hintergrund – Die Arbeitsweise von Windows ____________________________________ 7 4.6 Implementierung – Schlüsselwort message _______________________________________ 8 Konstruktor und Destruktor_________________________________________8 5.1 Objektvariablen und Instanzen _________________________________________________ 8 5.2 Der Konstruktor ____________________________________________________________ 9 5.3 Der Destruktor ____________________________________________________________ 10 Überschreiben von Methoden _______________________________________10 6.1 Aufruf geerbter Methoden – inherited __________________________________________ 11 7 Überladen von Methoden __________________________________________12 8 Felder und Eigenschaften __________________________________________12 8.1 9 Zugriffsmethoden __________________________________________________________ 12 Ereignisse _______________________________________________________13 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz 9.1 Prozedurale Typen _________________________________________________________ 14 9.2 Methodenzeiger ___________________________________________________________ 14 9.3 Auslösen von Ereignissen____________________________________________________ 15 10 Klassenmethoden _________________________________________________16 11 Forward-Deklarationen ____________________________________________16 12 Abstrakte Klassen ________________________________________________17 13 Weitere wichtige Schlüsselwörter ____________________________________19 13.1 Operator – is ______________________________________________________________ 19 13.2 Operator – as______________________________________________________________ 19 13.3 Parameter – Self ___________________________________________________________ 19 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz ALLGEMEINE EINFÜHRUNG Objektorientierte Programmierung (kurz OOP, Object Oriented Programming) ist eines der grundlegenden Konzepte moderner Softwareentwicklung. Die folgenden Seiten beziehen sich zwar speziell auf Delphi, treffen im Allgemeinen aber auch auf alle anderen objektorientierten Sprachen zu. OOP ist ein sehr komplexes und kompliziertes Thema. Anfangs ist es sehr schwer diese Thematik zu verstehen, aber mit hinreichend praktischen Erfahrungen fällt es einem wie Schuppen von den Augen und man lernt objektorientiert zu denken. Im Übrigen gehe ich davon aus, dass man mit der Object-Pascal Syntax vertraut ist und auch einige Grundkenntnisse besitzt. In einigen Kapiteln ist auch ein gutes Verständnis von Zeigern nötig. 1 Eigenschaften der objektorientierten Programmierung Die objektorientierte Programmierung sieht die Bestandteile eines Programms als eine Zusammenstellung voneinander weitgehend unabhängiger Teilprogramme an. Diese Teilprogramme werden Klassen genannt. Eine Klasse ist die in einer Programmiersprache geschriebene Konstruktionsvorschrift für ein Objekt. Das eigentliche Objekt entsteht in dem Augenblick, in dem der übersetzte Quellcode einer Klasse im Speicher aktiviert wird. Objekte stellen auf Anforderung dem System bestimmte Eigenschaften zur Verfügung. Klassen und Objekte sind eng verbundene Begriffe, die oft miteinander verwechselt werden, aber dennoch besteht ein großer Unterschied zwischen ihnen – das Verhältnis Objekt zu Klasse ist dasselbe wie das Verhältnis Variable zu Variablentyp. 2 Klassen 2.1 Was sind Klassen? Eine Klasse ist sozusagen die Vorlage für das Objekt. Sie enthält die Konstruktionsvorschrift wie das Objekt aufgebaut ist und ist somit die Voraussetzung für die erfolgreiche Initialisierung eines Objektes. Oft hört man auch den Begriff „Objekttyp“ – dieser ist gleichbedeutend mit dem Begriff „Klasse“. 2.2 Eigenschaften von Klassen Klassen haben drei wichtige Eigenschaften. Dies sind die Basisprinzipien durch die OOP erst so mächtig wird: 2.2.1 Kapselung Eine Klasse bildet eine in sich geschlossene Einheit, bei der nur bestimmte extra freizugebende Funktionen von außen zugänglich sind. Alle anderen Funktionen der Klasse sind private Funktionen und können nur innerhalb der Klasse aufgerufen werden. Gleiches gilt für die Daten, die eine Klasse bearbeitet und für die innerhalb der Klasse verwendeten Variablen. Diese sind, soweit nicht anders vereinbart, ebenfalls privat und somit vor Zugriffen von außen geschützt. Der Vorteil dieser Eigenschaft liegt auf der Hand: Von anderen Programmteilen kann weder auf Funktionen noch auf Daten einer Klasse unkontrolliert zugegriffen werden. Ist die Klasse selbst sauber programmiert und ausgetestet sind Seiteneffekte nahezu ausgeschlossen. 1 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz 2.2.2 Vererbung Es ist nicht sinnvoll, für jede erdenkliche Eigenschaft eines Programmteiles eine spezielle Klasse zu erstellen. Die Menge der dafür erforderlichen Klassen müsste gigantisch sein. Deswegen bietet OOP die Möglichkeit, dass eine Klasse seine Eigenschaften an eine neue Klasse mit zusätzlichen Eigenschaften weitergibt. Die neue beerbte Klasse wird abgeleitete Klasse (engl. derived class) genannt, die vererbende Klasse ist die Basisklasse (engl. base class). Die abgeleitete Klasse hat Zugriff auf zugelassene Bereiche der Basisklasse. Von außen sind auch in diesem Fall nur die extra freigegebenen Funktionen zugänglich. Ein gedachtes Beispiel veranschaulicht den Vorgang der Vererbung. Eine Basisklasse soll die Fähigkeit haben, auf einer GUI ein Fenster darzustellen. Dieses Fenster hat bestimmte Eigenschaften. Es hat wahlfreie Abmessungen, einen Rahmen, der eine leere Fläche umschließt, und kann auf dem Bildschirm auf einer bestimmten Position angezeigt werden. Von der Basisklasse wird nun eine Klasse abgeleitet, die die Eigenschaften des einfachen leeren Fensters mit Rahmen übernimmt und dahingehend erweitert eine Schaltfläche mit wahlfreiem Text, z.B. „OK“, darzustellen. Mit der auf diese Weise geschaffenen neuen Klassen können nun alle möglichen Schaltflächen mit den gewünschten Abmessungen und dem erforderlichen Text erzeugt und auf dem Bildschirm positioniert werden. (die VCL von Delphi wurde nach diesem Prinzip entwickelt) Dieses Beispiel zeigt auch einen weiteren Vorteil von OOP. Es ist nicht erforderlich, die komplexe Programmierung zur Erzeugung eines Fensters nachvollziehen. Um die abgeleitet Klasse zu bilden, sind nähere Kenntnisse über den Programmcode der Basisklasse nicht erforderlich. Es ist hinreichend, die Eigenschaften der Basisklasse und die Verfahren, diese Eigenschaften anzusprechen, zu kennen. 2.2.3 Polymorphie Polymorphie bedeutet soviel wie „Vielgestaltigkeit“ und hängt eng mit der Vererbung zusammen. Bei der reinen Vererbung enthalten abgeleitete Klassen alle Eigenschaften der Basisklasse und erweitern diese um zusätzliche, eigene, Eigenschaften. Wenn die Basisklasse entsprechend programmiert wurde, können Ableitungen dieser Klasse bestimmte Eigenschaften der Basisklasse nicht nur übernehmen sondern völlig verändern. Hierzu wird das Beispiel von vorher erweitert. Nach der oben beschriebenen Vererbung hat die Klasse zur Darstellung der Schaltflächen nur den Text in ein kleines Fenster eingefügt. Eine Schaltfläche macht aber einen optisch viel besseren Eindruck, wenn sie erhoben dargestellt wird. Dieser visuelle Eindruck kann mit einem anderen Rahmen um die Schaltfläche erzeugt werden. Die von der Basisklasse ererbte Eigenschaft, einen Rahmen um ein Fenster zu zeichnen, wird in der abgeleiteten Klasse verdeckt und durch die Eigenschaft, einen Rahmen mit 3D-Effekt zu zeichnen ersetzt. Die von der abgeleiteten Klasse erzeugten Schaltflächen werden nun alle mit dem neuen 3D-Rahmen dargestellt. 3 Objekte 3.1 Was ist ein Objekt? Ein Objekt in der Softwareentwicklung ist letztlich dasselbe wie in der realen Welt. (z.B. Auto). Das Objekt hat gewisse Eigenschaften (z.B. Farbe, aktuelle Geschwindigkeit, Höchstgeschwindigkeit, Gang, ...) und das Objekt kann bestimmte Tätigkeiten bzw. Befehle ausführen (z.B. Gas geben, bremsen, ...). 2 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz Der Programmierer versteht unter einem Objekt die Zusammenfassung (Kapselung) von Daten und den zugehörigen Funktionalitäten. Das Konzept der objektorientierten Programmierung (OOP) überwindet den prozeduralen Ansatz der klassischen Sprachen zugunsten einer realitätsnahen Modellierung. Prozedurale Programmierung bedeutet, dass man eine Reihe von Prozeduren und Funktionen, mit oder ohne Parameter hat, und diese vom Hauptprogramm aus aufgerufen werden. Das Konzept von OOP schließt jetzt mehrere solche Prozeduren zusammen und verpackt sie in ein sogenanntes „Objekt“. Prozeduren die einem Objekt zugeordnet sind nennt man Methoden. Ein Objekt besteht also aus Methoden (Prozeduren und Funktionen) und Attributen (Variablen die Daten enthalten). 3.2 Attribute und Methoden Der Begriff Objektfelder umfasst alle Variablen und Funktionen die von einem Objekt gekapselt werden. Wie bereits erwähnt nennt man solche Funktionen Methoden und die Variablen Attribute. Während Attribute den Zustand oder die Eigenschaften eines Objektes beschreiben, entsprechen die Methoden seinem Verhalten. Ein Objekt kann lediglich über eine Schnittstelle mit einem anderen Objekt kommunizieren, indem es dessen öffentliche (public) Methoden benutzt. Methoden sind Aktionen, die das Objekt durchführen kann. Die Attribute können dabei durch die sogenannten Zugriffsmethoden sozusagen geschützt werden. D.h. dass nur über diese Methoden auf ein Attribut zugegriffen werden kann. Bei den Attributen kann man unterscheiden zwischen Eigenschaften und Ereignissen. Eigenschaften repräsentieren die Daten, die ein Objekt enthält. Ereignisse sind Bedingungen, auf die das Objekt reagieren kann. 3 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz OBJEKTE IN DELPHI 1 Die Klassendeklaration Damit überhaupt erst mal ein Objekt entstehen kann muss man eine Klasse deklarieren. In Delphi ist eine solche Klassendeklaration wie folgt aufgebaut: type KlassenName = class(Basisklasse) private { private Deklarationen } protected { protected Deklarationen } public { public Deklarationen } published { published Deklarationen } end; Für KlassenName ist dabei der Name der neuen Klasse und für Basisklasse der Klassenname des Vorfahren einzutragen. Hinweis: In Delphi hat es sich eingebürgert alle Klassennamen mit einem großen „T“ zu beginnen. Diese Namensgebung mit vorangestelltem Präfix entstammt ursprünglich der ungarischen Notation. Es ist zwar keine Pflicht sich an diese Konvention zu halten, aber es ist trotzdem anzuraten, vor allem wenn diese Objekte veröffentlicht oder anderen Programmieren zur Verfügung gestellt werden. Mit dem Schlüsselwort „type“ wird eine Typendeklaration eingeleitet. Dazu gehören unter anderem Records und Klassen. Wird keine Basisklasse angegeben, so wird das Objekt automatisch von TObject abgeleitet (siehe auch Kapitel 2 - Aller Anfang ist TObject). 2 Aller Anfang ist TObject In Delphi sind alle Objekte automatisch vom Basisobjekt TObject abgeleitet – es ist sozusagen der Urahn aller VCL-Objekte und Komponenten. TObject ist folgendermaßen deklariert: { TObject class } type TObject = class constructor Create; procedure Free; class function InitInstance(Instance: Pointer): TObject; procedure CleanupInstance; function ClassType: TClass; class function ClassName: ShortString; class function ClassNameIs(const Name: String): Boolean; class function ClassParent: TClass; class function ClassInfo: Pointer; class function InstanceSize: Longint; class function InheritsFrom(AClass: TClass): Boolean; class function MethodAddress(const Name: ShortString): Pointer; 4 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz class function MethodName(Address: Pointer): ShortString; function FieldAddress(const Name: ShortString): Pointer; function GetInterface(const IID: TGUID; out Obj): Boolean; class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry; class function GetInterfaceTable: PInterfaceTable; function SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HResult; virtual; procedure AfterConstruction; virtual; procedure BeforeDestruction; virtual; procedure Dispatch(var Message); virtual; procedure DefaultHandler(var Message); virtual; class function NewInstance: TObject; virtual; procedure FreeInstance; virtual; destructor Destroy; virtual; end; TObject kapselt das grundlegende Verhalten, das allen Objekten in der VCL gemeinsam ist. Mit den von TObject eingeführten Methoden kann man: - Objektinstanzen erzeugen, verwalten und auflösen. Dies geschieht durch Zuweisen, Initialisieren und Freigeben von Speicher für das Objekt. - auf objektspezifische Informationen über den Klassentyp und die Instanz zugreifen und Laufzeitinformationen von Eigenschaften verwalten, die als published deklariert sind. - Botschaften individuell bearbeiten. - Schnittstellen, die das Objekt implementiert, unterstützen. TObject wird als direkte Basis für einfache Objekte verwendet, die nicht persistent sind (also nicht gespeichert und erneut geladen werden müssen) und die keinen anderen Objekte zugewiesen werden. Wenn bei der Deklaration einer neuen Objektklasse kein Vorfahr angegeben wird, setzt Delphi als Vorfahr automatisch die Klasse TObject ein. Die Leistungsfähigkeit von Delphi-Objekten beruht zu einem großen Teil auf den Methoden, die TObject einführt. Viele dieser Methoden sind für die interne Verwendung in der DelphiUmgebung vorgesehen, nicht aber für einen direkten Aufruf durch den Anwender. Andere Methoden müssen in abgeleiteten Objekten und Komponenten, die ein komplexeres Verhalten zeigen, überschrieben werden. 3 Kapselung Wie bereits erwähnt versteht man unter Kapselung das Verbergen der inneren Struktur eines Objekts. Die internen Daten und einige verborgene Methoden sind geschützt, also von außen nicht zugänglich. Die Manipulation des Objektes kann lediglich über streng definierte, über die Schnittstelle zur Verfügung gestellte, öffentliche Methoden erfolgen. In diesem Zusammenhang trifft man in jedem Fall auf die beiden Schlüsselwörter public und private: 3.1 private Auf ein private-Element kann nur innerhalb des Moduls (Unit oder Programm) zugegriffen werden, in dem die Klasse deklariert ist. Mit anderen Worten - eine private-Methode kann nicht von anderen Modulen aufgerufen werden, und als private deklarierte Felder oder Eigenschaften können nicht von anderen Modulen gelesen oder geschrieben werden. Indem 5 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz man wandte Klassendeklarationen im selben Modul zusammenfassen, man diesen Klassen also den Zugriff auf alle private-Elemente ermöglichen, ohne die Elemente anderen Modulen bekanntzumachen. 3.2 public Ein public-Element unterliegt keinerlei Zugriffsbeschränkungen. Es ist überall dort sichtbar, wo auf seine Klasse verwiesen werden kann. Die Benutzerschnittstelle einer Klasse stellt sich durch Elemente dar, die mit public vereinbart wurden. 3.3 protected Neben private und public gibt es noch ein drittes Schlüsselwort: protected 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. Es ist sozusagen ein „Mittelding“ zwischen private und public. Mit anderen Worten: auf ein protected-Element können alle Methoden einer Klasse zugreifen, die von der Klasse mit der Elementdeklaration abgeleitet ist. Mit diesem Sichtbarkeitsattribut werden also Elemente deklariert, die nur in den Implementierungen abgeleiteter Klassen verwendet werden sollen. Vor dem Zugriff von außen sind diese Elemente geschützt. 3.4 published Der Vollständigkeit halber sollte hier auch noch das vierte Schlüsselwort published erwähnt werden. 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. Delphi verwendet diese Informationen, um beim Speichern und Laden von Formulardateien (DFM) auf die Werte von Eigenschaften zuzugreifen, Eigenschaften im Objektinspektor anzuzeigen und spezielle Methoden (sogenannte Ereignisbehandlungsroutinen) bestimmten Eigenschaften (den Ereignissen) zuzuordnen. published-Eigenschaften sind auf bestimmte Datentypen beschränkt. Ordinal–, String–, Klassen–, Schnittstellen– 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 zwischen 0 und 31 hat (die Menge muß also in ein Byte, Wort oder Doppelwort passen). Auch alle Real-Typen außer Real48 können als published deklariert werden. Für Array-Eigenschaften kann dieser Gültigkeitsbereich nicht angegeben werden. 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 Schnittstellentyp haben. 4 Methoden Grundsätzlich gibt es drei verschiedene Arten von Methoden: 6 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz 4.1 Statische Methoden sind standardmäßig statisch. Statische Methoden können in abgeleiteten Klassen nicht überschrieben werden. Wenn in der abgeleiteten Klassen eine Methode mit demselben Namen deklariert wird, so wird die Methode der Basisklasse lediglich verdeckt, nicht jedoch überschrieben. Beim Aufruf bestimmt der deklarierte Typ (also der Typ zur Compilierzeit) der im Aufruf verwendeten Klassen- bzw. Objektvariable, welche Implementierung aktiviert wird. 4.2 Virtuelle Virtuelle Methoden können im Gegensatz zu statischen Methoden in abgeleiteten Klassen überschrieben werden. Beim Aufrufen einer überschriebenen Methode bestimmt nicht der deklarierte, sondern der aktuelle Typ (also der Typ zur Laufzeit) der im Aufruf verwendeten Klassen- bzw. Objektvariable, welche Implementierung aktiviert wird. 4.3 Dynamische Virtuelle und dynamische Methoden sind von der Semantik her identisch – sie können beide in abgeleiteten Klassen überschrieben werden. Sie unterscheiden sich nur bei der Implementierung der Aufrufverteilung zur Laufzeit. Virtuelle .Methoden werden auf Geschwindigkeit, dynamische Methoden auf Code-Größe optimiert. Im Allgemeinen kann mit virtuellen Methoden polymorphes Verhalten am effizientesten implementiert werden. Dynamische Methoden sind hilfreich, wenn in einer Basisklasse eine große Anzahl überschreibbarer Methoden deklariert ist, die von vielen abgeleiteten Klassen geerbt, aber nur selten überschrieben werden. 4.4 Botschaftsverarbeitende Methoden Diese Methodenart passt zwar nicht in das Schema der vorherigen drei, ist aber auch eine spezielle Methodenart und wird deshalb in ebenfalls in diesem Kapitel beschrieben. Um den Sinn dieser Methodenart zu verstehen muss man ein bisschen die Hintergründe und Arbeitsweise von Windows kennen. Windows ist ein sogenanntes nachrichtenbasiertes Betriebssystem. Ich gehe hier nicht auf Details ein sondern erkläre nur kurz ein bisschen das Prinzip. 4.5 Hintergrund – Die Arbeitsweise von Windows Wenn z.B. mit der Maus geklickt wird, oder eine Taste der Tastatur gedrückt wird, so wird von Windows eine entsprechende Message erzeugt. Wenn die linke Maustaste gedrückt wird ist dies WM_LBUTTONDOWN und wenn sie wieder losgelassen wird, wird entsprechend die WM_LBUTTONUP Nachricht erzeugt. Der Präfix WM steht dabei für WindowsMessage. Sobald diese Nachricht erzeugt wurde wird sie an das betreffende Fenster geschickt. Bei einem Mausklick ist das betroffene Fenster also jenes über dem sich die Maus zu diesem Zeitpunkt befindet. (Wichtig: In Windows sind alle Steuerelemente „Fenster“ und können daher Nachrichten empfangen!) Um nun eine solche Nachricht empfangen zu können hat jedes Fenster eine Message-Loop und eine Fensterprozedur. In der Message-Loop werden die eintreffenden Nachrichten abgelegt und in der Fensterprozedur werden sie ausgelesen und verarbeitet. Über eine Botschaftsverarbeitende Methode kann man nun auf solche eintreffenden Nachrichten reagieren. Voraussetzung für eine Botschaftsverarbeitende Methode ist ein gültiges Fensterhandle und eine korrekte Fensterprozedur. Entweder 7 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz implementiert man diese Punkte selbst direkt über das Win32-API oder aber man leitet sein Objekt von TWinControl ab. Dieses Objekt implementiert bereits alle wichtigen Aspekte. 4.6 Implementierung – Schlüsselwort message Eine Botschaftsverarbeitende Methode wird mit dem Schlüsselwort message und dem darauffolgenden Message-Namen gekennzeichnet. Beispiel: type TMyWinControl = class(TWinControl) protected procedure WMLButtonDown(Msg: TMessage); message WM_LBUTTONDOWN; end; Alle botschaftsverarbeitenden Methoden brauchen einen Parameter über den man an die Informationen der Nachricht kommt. Da alle Messages gleich aufgebaut sind gibt es so etwas wie einen „Universal-Record“. Dieser ist eben TMessage. D.h. jede botschaftsverarbeitende Methode kann einen Parameter vom Typ TMessage haben. Neben TMessage sind der Unit Messages noch andere nachrichtenspezifische Records definiert über die eine einfachere Abfrage der Informationen möglich ist. 5 Konstruktor und Destruktor Eine objektorientierte Sprache wie Delphi realisiert das Erzeugen und Entfernen von Objekten mit sogenannten Konstruktoren und Destruktoren. Beides sind spezielle Methoden innerhalb der Klassendeklaration. Der Konstruktor ist dafür verantwortlich, dass überhaupt erst ein Objekt entstehen kann. Er wird bei der Initialisierung, also bei der Erzeugung, eines Objektes aufgerufen und führt alle zur Initialisierung nötigen Aufgaben wie z.B. Allozierung von Speicher und initialisieren der Objektfelder aus. Der Destruktor wird beim Entfernen von Objekten aufgerufen und ist für sämtliche Aufräumarbeiten wie z.B. freigeben von alloziertem Speicher verantwortlich. 5.1 Objektvariablen und Instanzen Um mit Objekten arbeiten zu können, muss eine Objektvariable als sogenannte Instanz der Klasse gebildet werden. Im Gegensatz zu normalen Variablendeklarationen ist es hier mit der reinen Deklaration noch nicht getan sondern bedarf folgender drei Schritte: • Mit var wird zunächst eine Referenz auf ein Objekt des gewünschten Typs deklariert. Dies ist ein Zeiger, der momentan noch den Wert nil hat, also gewissermaßen „in die Luft“ oder auch „nirgendwo“ hinzeigt. • Durch Aufruf des sogenannten Konstruktors (meist ist dies die Create-Methode) wird eine Instanz der Klasse gebildet. Erst jetzt entsteht das eigentliche Objekt und der im ersten Schritt deklarierte Zeiger (Referenz) verweist auf einen konkreten Speicherbereich. • Wenn das Objekt nicht mehr benötigt wird, sollte der von ihm belegte Speicher wieder freigegeben werden. Dies geschieht durch den Aufruf eines sogenannten Destruktors (meist ist dies die Free-Methode). Um Missverständnisse vorzubeugen sollte nach Aufruf des Destruktors der Objektvariablen wieder der Wert nil 8 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz zugewiesen werden, damit nicht auf das nicht mehr vorhandene Objekt und damit auf nicht reservierten Speicher zugegriffen wird. Beispiel: var MyObject: TMyObject; begin MyObject := TMyObject.Create; .... MyObject.Free; end; // MyObject muss erst instanziert werden // arbeiten mit MyObject // Objekt wieder freigeben Die Instanz eines Objektes ist ein dynamisch reservierter Speicherbereich mit einer internen Struktur, die vom Objekttyp abhängt. Jedes Objekt besitzt eine mit eigenen Werten gefüllte Kopie der im Objekttyp deklarierten Felder, benutzt aber die Methoden unverändert mit anderen Objekten zusammen. Ein Objekt ist lediglich eine Referenz auf ein bestimmtes Objekt der Klasse. Sie enthält also nicht bereits das Objekt, sondern stellt einen Zeiger auf den Speicherbereich des Objektes bereit. Diese Zeiger werden, im Gegensatz zu den in C++ üblichen Objekten, von Delphi intern dereferenziert. Es können sich so mehrere Objektvariablen auf ein und dieselbe Instanz beziehen. Wenn eine Objektvariable den Wert nil enthält, so bedeutet das, dass sie momentan kein Objekt referenziert. 5.2 Der Konstruktor Die Deklaration eines Konstruktors gleicht einer normalen Prozedurdeklaration, beginnt aber mit dem Wort constructor. Beispiel: type TMyObject = class(TObject) public constructor Create; override; // der Konstruktor Info: String; // Variable die im Konstruktor initialisiert wird end; Der Implementations-Teil schaut dann so aus: constructor TMyObject.Create; begin inherited; Info := ‘Dieser Text wurde im Konstruktor zugewiesen’ end; Die beiden Schlüsselwörter override und inherited werden im Kapitel 6 – „Überschreiben von Methoden“ noch beschrieben. Eine Klasse kann durchaus mehrere Konstruktoren deklarieren bzw. implementieren. Diese müssen dann eventuell unterschiedliche Methodennamen haben oder mit der Direktive overload gekennzeichnet sein (siehe Kapitel 7 – Überladen von Methoden). 9 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz 5.3 Der Destruktor Ein Destruktor ist eine spezielle Methode, die ein Objekt im Speicher freigibt. Die Deklaration eines Destruktors gleicht, genauso wie schon die des Konstruktors, der einer Prozedur, beginnt aber dem Namen entsprechend mit dem Wort destructor. Destruktoren müssen die Standardaufrufkonvention register verwenden. Obwohl in einer Klasse mehrere Destruktoren implementiert werden können, ist es ratsam, nur die geerbte Destroy-Methode zu überschreiben und keine weiteren Destruktoren zu deklarieren. Beispiel: type TMyObject = class(TObject) public destructor Destroy; override; end; // der Destruktor Der Implementations-Teil schaut dann so aus: destructor TMyObject.Destroy; begin .... // alle notigen Freigaben und ähnliches inherited; end; Die beiden Schlüsselwörter override und inherited werden im Kapitel 6 – „Überschreiben von Methoden“ noch beschrieben. Ein Destruktor kann nur über ein Instanzobjekt aufgerufen werden. MyObject.Destroy; Beim Aufruf eines Destruktors werden zuerst die in der Implementierung angegebenen Aktionen ausgeführt. Normalerweise werden hier untergeordnete Objekte und zugewiesene Ressourcen freigegeben. Danach wird das Objekt im Speicher freigegeben. Wenn beim Erstellen eines Objekts eine Exception auftritt, wird das unvollständige Objekt automatisch durch einen Aufruf von Destroy freigegeben. Der Destruktor muß daher auch in der Lage sein, Objekte freizugeben, die nur teilweise erstellt wurden. Da im Konstruktor alle Felder eines neuen Objekts mit Null initialisiert werden, haben Klassenreferenz- und Zeigerfelder in einer unvollständigen Instanz immer den Wert nil. Man sollte solche Felder im Destruktor immer auf den Wert nil testen, bevor man Operationen mit ihnen durchführen. Wenn man Objekte nicht mit Destroy, sondern mit der Methode Free (von TObject) freigeben, wird diese Prüfung automatisch durchgeführt. 6 Überschreiben von Methoden Um eine Methode zu überschreiben, braucht sie nur mit der Direktiven override erneut deklariert zu werden. Dabei müssen Reihenfolge und Typ der Parameter sowie der Typ des Rückgabewertes (falls vorhanden) mit der Deklaration in der Basisklasse übereinstimmen. Im folgenden Beispiel wird die in der Klasse TFigure deklarierte Methode Draw in zwei abgeleiteten Klassen überschrieben: 10 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz type TFigure = class procedure Draw; virtual; end; TRectangle = class(TFigure) procedure Draw; override; end; TEllipse = class(TFigure) procedure Draw; override; end; Ausgehend von diesen Deklarationen zeigt der folgende Programmcode, wie sich der Aufruf einer virtuellen Methode durch eine Variable auswirkt, deren aktueller Typ zur Laufzeit geändert wird. var Figure: TFigure; begin Figure := TRectangle.Create; Figure.Draw; // Ruft TRectangle.Draw auf. Figure.Destroy; Figure := TEllipse.Create; Figure.Draw; // Ruft TEllipse.Draw auf. Figure.Destroy; end; 6.1 Aufruf geerbter Methoden – inherited In überschriebenen Methoden ist es oft notwendig die alte Verhaltensweise des Objektes beizubehalten und nur neue Initialisierungen oder Reaktionen einzubauen. Um nicht den gesamten Code „nachbauen“ zu müssen gibt es in Delphi die Möglichkeit die Originalmethode der Basisklasse aufzurufen. Das reservierte Wort inherited ist für die Polymorphie von großer Bedeutung. Es kann in Methodenimplementierungen (mit oder ohne nachfolgenden Bezeichner) angegeben werden. Folgt auf inherited ein Methodenbezeichner, entspricht dies einem normalen Methodenaufruf. Der einzige Unterschied besteht darin, daß die Suche nach der Methode bei dem direkten Vorfahren der Klasse beginnt, zu der die Methode gehört. Durch die folgende Anweisung wird beispielsweise die geerbte Methode Create aufgerufen: inherited Create(...); Die Anweisung inherited ohne Bezeichner verweist auf die geerbte Methode mit demselben Namen wie die aufrufende Methode. In diesem Fall kann inherited mit oder ohne Parameter angegeben werden. Ohne Parameterangabe werden der geerbten Methode einfach die Parameter der aufrufenden Methode übergeben. So wird beispielsweise inherited; 11 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz häufig in der Implementierung von Konstruktoren verwendet. Der geerbte Konstruktor wird also mit den Parametern aufgerufen, die an die abgeleitete Klasse übergeben wurden. In einem Konstruktor sollte inherited immer als erstes aufgerufen werden, damit alle Initialisierungen des Basisobjekts fertig sind, bevor man selbst mit dem Objekt arbeitet. Im Destruktor ist es dafür genau umgekehrt. Hier sollten zuerst alle eigenen Speicher bzw. Objektfreigaben stattfinden bevor man über inherited den Destruktor des Basisobjektes aufruft. 7 Überladen von Methoden Nur virtuelle und dynamische Methoden können überschrieben werden, es können jedoch alle Methoden überladen werden. Unter dem „überladen von Methoden“ versteht man, dass eine Klasse mehrere Methoden mit demselben Methodennamen deklarieren kann. Diese Methoden müssen sich jedoch entweder in Parameteranzahl oder Parameterart unterscheiden. Der Compiler sucht dann beim kompilieren automatisch die passende Methode aus. Um eine Methode zu überladen muss sie mit der Anweisung overload neu deklariert werden. Wenn sich die Parameterangaben von denen ihres Vorfahren unterscheiden, wird die geerbte Methode überladen, ohne daß sie dadurch verdeckt wird. Bei einem Aufruf der Methode in einer abgeleiteten Klasse wird dann diejenige Implementierung aktiviert, bei der die Parameter übereinstimmen. 8 Felder und Eigenschaften Eigenschaften (Properties) und Felder eines Objektes werden gemeinsam auch als dessen Attribute bezeichnet. Felder belegen „normale“ Speicherplätze wie z.B. die Felder eines Records und können demnach direkt gelesen bzw. geändert/gesetzt werden. Objekteigenschaften werden mit dem Schlüsselwort property deklariert und erscheinen nach außen wie normale Objektfelder, allerdings kapseln sie intern nur Methoden zum Lesen (read) bzw. Schreiben (write) des Wertes eines oder mehrerer Felder und speichern selbst keine Werte. Diese mit read bzw. write spezifizierten Methoden nennt man Zugriffsmethoden. 8.1 Zugriffsmethoden Zugriffsmethoden sind dazu da, um Objektfelder von außen zu schützen (z.B. ReadOnlyEigenschaften) oder Zuweisungen einer Gültigkeitskontrolle zu unterziehen. Durch Weglassen von write lässt sie beispielsweise der Schreibzugriff verwehren. Zugriffsmethoden werden meist entweder als public oder aber als protected deklariert. Die Deklaration als protected hat den Vorteil, dass abgeleitete Klassen die Methode den Umständen entsprechend anpassen bzw. verändern kann (vorausgesetzt sie wurde als virtual oder dynamic deklariert). Die Deklaration einer Eigenschaft muß einen Namen, einen Typ und mindestens eine Zugriffsangabe enthalten. Die Syntax lautet folgendermaßen: property Eigenschaftsname[Indizes]:Typ indexInteger-Konstante Bezeichner; • Eigenschaftsname ist ein beliebiger gültiger Bezeichner. • [Indizes] ist optional und besteht aus einer Folge von durch Semikolons getrennten Parameterdeklarationen. Jede Deklaration hat die Form Bezeichner1 ... 12 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz Bezeichnern: Typ. Weitere Informationen hierzu finden Sie im Abschnitt ArrayEigenschaften. • Die Klausel index Integer-Konstante ist optional (siehe Indexangaben). • Bezeichner ist eine Folge von read, write, stored, default (oder nodefault) und implements-Angaben. Jede Eigenschaftsdeklaration muß zumindest einen readoder write-Bezeichner enthalten. Eigenschaften werden durch ihre Zugriffsangaben definiert. Sie können im Gegensatz zu Feldern nicht als var-Parameter übergeben oder mit dem Adreßoperator @ versehen werden. Der Grund dafür liegt darin, daß eine Eigenschaft nicht notwendigerweise im Speicher vorhanden sein muß. Sie kann beispielsweise eine read-Methode haben, die einen Wert aus einer Datenbank abruft oder einen Zufallswert generiert. Beispiel: type TAuto = class(TObject) private FTempo: Integer; public constructor Create; procedure Gasgeben; procedure Bremsen; property Geschwindigkeit: Integer read FTempo; end; { implementation } constructor TAuto.Create; begin inherited; FTempo := 0; end; procedure TAuto.Gasgeben; begin Inc(FTempo, 5); end; procedure TAuto.Bremsen; begin Dec(FTemp, 5); end; Das Objekt Auto implementiert einen Konstruktor, zwei Methoden und eine Eigenschaft. Im Konstruktor wird dafür gesorgt, dass die Startgeschwindigkeit 0 ist. 9 Ereignisse Ereignisse sind eine Sonderform von Eigenschaften. Um das Prinzip von Ereignissen zu verstehen ist allerdings ein gutes Verständnis von Pointern erforderlich. Jede Funktion oder Prozedur ist eigentlich nur ein Block im Arbeitsspeicher. Daher kann man mit einem Pointer mit der Adresse auf diese Speicherstelle auch einen Pointer auf eine 13 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz Funktion/Prozedur haben. Diese Pointer nennt man Prozedurzeiger. Und genau nach diesem Prinzip funktionieren Ereignisse. Ein Ereignis ist nur ein Pointer auf eine Prozedur, die bei der Auslösung des Ereignisses aufgerufen wird. Um aber so ein Ereignis zu implementieren bedarf es ein paar Vorbereitungen. 9.1 Prozedurale Typen Definieren wir mal eine Funktion mit dem Namen Calc, die zwei Integer-Parameter übernimmt und einen Integer-Wert zurückgibt: function Calc(X, Y: Integer): Integer; Man kann die Calc-Funktion nun der Variablen F zuweisen und aufrufen: var F: function(X, Y: Integer): Integer; Ergebnis: Integer; begin F := Calc; Ergebnis := F(3, 5); end; Der Bezeichner hinter dem Schlüsselwort procedure bzw. function ist der Name des prozeduralen Typs. Dieser Typname kann direkt in einer Variablendeklaration (siehe oben) verwendet oder zur Deklaration neuer Typen benutzt werden: type TIntegerFunction = function: Integer; TProcedure = procedure; TStrProc = procedure(const S: string); TMathFunc = function(X: Double): Double; var F: TIntegerFunction; { F ist eine parameterlose Funktion, die einen Integer zurückgibt } Proc: TProcedure; { Proc ist eine parameterlose Prozedur } SP: TStrProc; { SP ist eine Prozedur, die einen String- Parameter übernimmt } M: TMathFunc; { M ist eine Funktion, die einen Double-Parameter (reeller Wert) übernimmt und einen Double-Wert zurückgibt } Diese Typen können beliebig eingesetzt werden. z.B. so: procedure FuncProc(P: TIntegerFunction); { FuncProc ist eine Prozedur, deren einziger Parameter eine parameterlose Funktion ist, die einen Integer zurückgibt } 9.2 Methodenzeiger Die Deklaration von Methodenzeigern unterscheidet sich nur geringfügig von der von Prozedurzeigern. Um eine Methode eines Instanzobjekts zur , muss dem Namen des prozeduralen Typs die Klausel of object hinzugefügt werden: 14 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz type TMethod = procedure of object; TNotifyEvent = procedure(Sender: TObject) of object; Ein Methodenzeiger wird in Form zweier Zeiger codiert, von denen der erste die Adresse der Methode speichert. Der zweite enthält eine Referenz auf das Objekt, zu dem die Methode gehört. Beispiel: type TNotifyEvent = procedure(Sender: TObject) of object; TMainForm = class(TForm) procedure ButtonClick(Sender: TObject); ... end; var MainForm: TMainForm; OnClick: TNotifyEvent; Nach diesen Deklarationen ist die folgende Zuweisung korrekt: OnClick := MainForm.ButtonClick; Zwei prozedurale Typen sind kompatibel, wenn sie folgende Bedingungen erfüllen: • Sie verwenden dieselbe Aufrufkonvention. • Sie haben denselben (oder keinen) Rückgabetyp. • Sie verwenden eine identische Anzahl von Parametern. Die Parameter an einer bestimmten Position müssen identische Typen haben (die Parameternamen spielen keine Rolle). Zeiger auf Prozeduren sind niemals kompatibel zu Zeigern auf Methoden. Der Wert nil kann jedem prozeduralen Typ zugewiesen werden. 9.3 Auslösen von Ereignissen Um ein Ereignis auszulösen reicht ein einfacher Aufruf über den Methodenzeiger. Vor dem Aufruf muss allerdings überprüft werden, ob der Zeiger nicht den Wert nil hat, denn dann sonst könnte es zu potentiellen Zugriffsfehlern kommen. Eine typische Deklaration bzw. Implementation von Ereignissen inklusive Aufruf sieht etwa so aus: type TMyObject = class(TObject) private .... FOnClick: TNotifyEvent; public .... procedure Click; property OnClick: TNotifyEvent read FOnClick write FOnClick; end; 15 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz procedure TControl.Click; begin if Assigned(FOnClick) then FOnClick(Self); end; 10 Klassenmethoden Eine Klassenmethode arbeitet nicht mit Objekten sondern mit Klassen. Für den Aufruf einer Klassenmethode ist deshalb keine Objektvariable sondern lediglich die Klassendeklaration und Implementierung notwendig. Deklaration und Implementierung müssen mit dem reservierten Wort class eingeleitet werden. Beispiel: type TMyObject = class(TMyObject) public class function VersionsInfo: String; end; { Implementation } class function TMyObject.VersionsInfo: String; begin Result := ‘Version 1.0 der Beispielklasse für Klassenmethoden’; end; Klassenmethoden sind von Anfang an im Speicher vorhanden – im Gegensatz zu Objekten, die erst durch einen Konstruktor ins Leben gerufen werden müssen. Deshalb darf man von einer Klassenmethode natürlich nicht auf Objektfelder der Klasse zugreifen, da kein Objekt und damit auch keine Objektfelder existieren. Würde der Compiler einen solchen Zugriff nicht schon zur Entwurfszeit als Fehler erkennen und markieren, würde es zur Laufzeit zu potentiellen Zugriffsfehlern kommen. 11 Forward-Deklarationen Manchmal ist es nötig, dass zwei unterschiedliche Klassen die jeweils andere referenziert: type TFirstClass = class(TObject) public Second : TSecondClass; end; TSecondClass = class(TObject) public First : TFirstClass; end; Allerdings ahndet der Compiler diese Deklaration beinhart mit einer Fehlermeldung, da in der Deklaration von TMyClass1 noch nichts von der Klasse TMyClass2 bekannt ist. Um dieses Problem zu umgehen setzt man Forward-Deklarationen, auf Deutsch Vortwärtsdeklarationen, ein. Forward-Deklarationen sehen folgendermaßen aus: 16 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz type TFirstClass = class; // Forward-Deklaration von TFirstClass TSecondClass = class; // Forward-Deklaration von TSecondClass TFirstClass = class(TObject) public Second: TSecondClass; end; TSecondClass = class(TObject) public First: TFirstClas ; end; Eine Forward-Deklaration funktioniert immer nach dem Schema: Klassenname = class; Das abschließende Semikolon gibt dem Compiler zu erkennen, dass es sich um eine ForwardDeklaration handelt und die eigentliche Deklaration der Klasse erst später kommt. 12 Abstrakte Klassen Abstrakte Klassen sind Klassen, die gewisse Methoden zwar deklarieren, aber nicht implementieren. Solche Methoden nennt man „abstrakte Methoden“. Unter Delphi deklariert man eine solche Methode durch das Wort abstract das der Funktions- /Prozedur-Deklaration nachgestellt wird. z.B.: procedure Irgendwas; virtual; abstract; Natürlich müssen abstrakte Methoden immer als virtual oder dynamic deklariert werden, damit sie von abgeleiteten Klassen überschrieben und implementiert werden können. Eine abstrakte Methode kann nur in einer Klasse (bzw. Instanz einer Klasse) aufgerufen werden, in der sie überschrieben wurde. Der Sinn solcher Klassen ist Anfangs nicht ganz klar ersichtlich, aber mit einem kleinen Bsp. versteht man die Idee die dahinter steckt. Ein gutes Beispiel stellt die abstrakte Klasse TStream dar: { TStream abstract class } type TStream = class(TObject) private function GetPosition: Longint; procedure SetPosition(Pos: Longint); function GetSize: Longint; protected procedure SetSize(NewSize: Longint); virtual; public function Read(var Buffer; Count: Longint): Longint; virtual; abstract; function Write(const Buffer; Count: Longint): Longint; virtual; abstract; function Seek(Offset: Longint; Origin: Word): Longint; virtual; abstract; procedure ReadBuffer(var Buffer; Count: Longint); procedure WriteBuffer(const Buffer; Count: Longint); 17 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz function CopyFrom(Source: TStream; Count: Longint): Longint; function ReadComponent(Instance: TComponent): TComponent; function ReadComponentRes(Instance: TComponent): TComponent; procedure WriteComponent(Instance: TComponent); procedure WriteComponentRes(const ResName: String; Instance: TComponent); procedure WriteDescendent(Instance, Ancestor: TComponent); procedure WriteDescendentRes(const ResName: String; Instance, Ancestor: TComponent); procedure WriteResourceHeader(const ResName: string; out FixupInfo: Integer); procedure FixupResourceHeader(FixupInfo: Integer); procedure ReadResHeader; property Position: Longint read GetPosition write SetPosition; property Size: Longint read GetSize write SetSize; end; Es gibt mehrere verschiedene Stream-Arten: - TFileStream (für Dateien) - TStringStream (zur Bearbeitung von Strings im Speicher) - TMemoryStream (für den Speicherpuffer) - TBlobStream (für BLOB-Felder) - TWinSocketStream (zum Lesen und Schreiben über eine Socket-Verbindung) - TOleStream (zum Lesen und Schreiben über eine COM-Schnittstelle) Alle diese Stream-Arten sind von der abstrakten Klasse TStream abgeleitet. Jede dieser Streams implementiert die in TStream nicht implementierten, also abstrakten, Methoden. Damit haben alle Streams dieselben Methoden mit gleichen Namen und gleichen Parametern. Dadurch ergibt sich eine gewisse Kompatibilität. Zum Beispiel implementiert TStream die Methode CopyFrom(). Diese Methode verlangt als Parameter ebenfalls ein TStream-Objekt und verwendet intern nur die abstrakten Methoden wie Read und Write. Da schließlich alle Nachkommen von TStream diese abstrakten Methoden implementieren, ist z.B. ein Aufruf nach folgendem Schema möglich: TFileStream.CopyFrom(TMemoryStream, 0); Statt den Klassennamen müssen natürlich Objekte eingesetzt werden, aber dieses Beispiel soll schließlich nur das Zusammenspiel zweier Klassen veranschaulichen, die beide dieselbe abstrakte Basisklasse haben. Das Beispiel könnte genauso gut umgekehrt lauten: TMemoryStream.CopyFrom(TFileStream, 0); Oder aber auch ganz anders: TWinSocketStream.CopyFrom(TStringStream, 0); Es würde in jedem Fall zum gewünschten Ergebnis führen. Damit sollte auch der Sinn von abstrakten Klassen geklärt sein. Abstrakte Klassen sind dazu da, um gewisse Protokolle zu erzwingen und damit Kompatibilitäten zu schaffen. 18 Spezialgebiet – Informatik OOP in Delphi Manuel Pöter GRG XV Auf der Schmelz 13 Weitere wichtige Schlüsselwörter 13.1 Operator – is Der Operator is führt eine dynamische Typprüfung durch. Mit ihm kann man den aktuellen Laufzeittyp eines Objekts ermitteln. Der Ausdruck Objekt is Klasse gibt True zurück, wenn Objekt eine Instanz der angegebenen Klasse oder eines ihrer Nachkommen ist. Trifft dies nicht zu, wird False zurückgegeben (hat Objekt den Wert nil, ist der Rückgabewert ebenfalls False). Wenn der deklarierte Typ von Objekt nicht mit Klasse verwandt ist (wenn die Typen also unterschiedlich und nicht voneinander abgeleitet sind), gibt der Compiler eine Fehlermeldung aus. Ein Beispiel: if ActiveControl is TEdit then TEdit(ActiveControl).SelectAll; Diese Anweisung prüft zuerst, ob die Variable eine Instanz von TEdit oder einem ihrer Nachkommen ist, und führt anschließend eine Typumwandlung in TEdit durch. 13.2 Operator – as Der Operator as führt eine Typumwandlung mit Laufzeitprüfung durch. Der Ausdruck Objekt as Klasse gibt eine Referenz auf dasselbe Objekt wie Objekt, aber mit dem von Klasse angegebenen Typ zurück. Zur Laufzeit muß Objekt eine Instanz von Klasse oder einem ihrer Nachkommen bzw. nil sein. Andernfalls wird eine Exception ausgelöst. Wenn der deklarierte Typ von Objekt nicht mit Klasse verwandt ist (wenn die Typen also unterschiedlich und nicht voneinander abgeleitet sind), gibt der Compiler eine Fehlermeldung aus. Ein Beispiel: with Sender as TButton do begin Caption := '&Ok'; OnClick := OkClick; end; Die Regeln der Auswertungsreihenfolge machen es häufig erforderlich, as-Typumwandlungen in Klammern zu setzen: (Sender as TButton).Caption := '&Ok'; 13.3 Parameter – Self Der Bezeichner Self verweist in der Implementierung einer Methode auf das Objekt, in dem die Methode aufgerufen wird. Es ist also ein Pointer „auf sich selbst“ und entspricht dem „this” aus C/C++. Self ist in vielen Situationen hilfreich. Wenn beispielsweise ein Element in einer Methode seiner Klasse erneut deklariert wird, kann mit Self.Bezeichner auf das Originalelement zugegriffen werden. 19