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