Home
Up
Intro
Contents
Chapter
1
2
3
4
5
6
7
8
9
10
Design
Assert
Timing
EBNF
Report
Pas
Last Changed: Nov. 19, 1997
This is a conversion from Oberon text to HTML. The converter software is still under development,
and some features or information may be missing in this converted version.
HTML hypertext facilities are not yet active in this document.
To exploit the interactive facilities, use Oberon System 3 and the source of this text,
available as ASCII-coded Oberon System 3 documents. A previous version is also available for Oberon V4.
To access this and other additional material use
ftp.
For the convenience of our students, most of this information and the related material is in German.
Sorry if this is not one of your languages.
Einführung in die Programmiersprache Oberon.
9 Objekte und Nachrichten
Oberon-Programme können
erweitert werden - selbst über einen langen Zeitraum hinweg, auch nachdem
die Entwicklung des ursprünglichen Programms abgeschlossen ist, und
möglicherweise auch während das Programm läuft. Das eindrucksvollste
Beispiel ist Oberon selbst: Oberon kennt zum Beispiel den abstrakten Typ
Display.Frame. Konkrete Implementierungen sind zum Beispiel Variable vom
Typ TextFrames.Frame. Diese sind standardmässig in Oberon implementiert.
Im Laufe der Zeit sind Varianten hinzugekommen, die weitere Möglichkeiten
bieten, ohne dass deswegen das Modul Display geändert werden musste.
Lange nach Entwicklung der Standard-Oberon-Frames ist es immer noch möglich,
Frames neu zu definieren, die vorher ungedachte Möglichkeiten besitzen
- etwa Frames, die aktive Internet-Verbindungen oder audio-visuelle Möglichkeiten
nutzen.
Diese Erweiterbarkeit braucht keine neuen Sprachkonstrukte, sondern nutzt
nur die bekannten Spracheigenschaften aus - im wesentlichen die Erweiterbarkeit
abstrakter Datentypen, die wir bereits kennengelernt haben. Diese wird für
einen bestimmten Programmierstil eingesetzt: für objektorientierte
Programmierung.
Der Gegensatz dazu ist eine prozedurale Programmierung - im einfachsten Fall
eine Festlegung des Programmablaufs Schritt für Schritt. Bei der objektorientierten
Programmierung hingegen gehen wir davon aus, das jedes Objekt spezifische
Methoden hat, wie es auf Anforderungen reagiert. Wir orientieren uns nicht
an den einzelnen Programmschritten, sondern daran, welche Anforderung an
welches Objekt gestellt wird. An einen Display.Frame kann etwa die Anforderung
gestellt werden: Zeige dich! oder: verändere deine Grösse! oder...
Die erforderliche Reaktion kann je nach Art des Display.Frames unterschiedlich
sein, und Display.Frames unterschiedlicher Art können im konkreten
Fall auftreten. Man spricht von Polymorphismus.
Die Information, die zur Erfüllung der Anforderungen notwendig ist,
kann unterschiedlich sein. Ein Text-Frame etwa braucht Information über
den Text, der in ihm gezeigt werden soll, während ein Netzwerk-Frame
lediglich eine entsprechende Netzwerkreferenz braucht und ein Audio-Controller
wieder ganz andere Information benötigt. Bei objektorientierter Programmierung
geht man davon aus, dass Zustandsinformation und Verweise auf die Art, wie
auf Anforderungen zu reagieren ist, in der Definition des Objekts verkapselt
sind.
Die Deklaration von Display.Frame im Modul Display basiert im Prinzip auf
Frame*=POINTER
TO FrameDesc;
FrameDesc*=RECORD
....
X*, Y*, W*, H*: INTEGER;
handler*: PROCEDURE;
...
END;
Ein TextFrames.Frame hingegen hat alle diese Einträge, und zusätzlich
Verweise auf den Text. Mit der Sprachmöglichkeit, Datentypen zu erweitern,
brauchen wir die bereits bei Display.Frame benutzten Informationen nicht
zu wiederholen, sondern können in einem Modul TextFrames definieren.
Die als Erweiterung deklarierten Datentypen erben alle Einträge
von ihren Vorgängern, und können diese um eigene Felder ergänzen.
FrameDesc*=RECORD(Display.FrameDesc)
text*: Texts.Text;
...
selbeg*, selend*:
Location (* current selection *)
END;
Dabei steht "handler" in der Deklaration von Display.Frame
für eine Prozedur, die die Reaktionen von Variablen vom Typ Display.Frame
auf Anforderungen festlegt. Verschiedene Anforderungen können an Variablen
vom Typ Display.Frame gestellt werden. Daher müssen wir die Prozedur
parametrisieren, um eine Möglichkeit zu haben, ihr die Art der Anforderung
mitzuteilen. Die Alternative wäre, für jede Art von Anforderung
eine getrennte Prozedur einführen - eine wenig praktikable und nicht
erweiterbare Alternative. Der "Slot" für die Prozedur "handler" wird
an TextFrames.Frame vererbt und neue Anforderungen können an Variablen
vom Typ TextFrames.Frame gestellt werden. Damit verbietet sich im Hinblick
auf eine mögliche Erweiterbarkeit die Idee, für jede Anforderung
eine getrennte Prozedur bereitzustellen. Es verbietet sich auch, die Anforderungen
über eine Aufzählung mit einem Basis-Datentypen zu kennzeichnen
- dies würde später keine Erweiterbarkeit erlauben. Schliesslich
ist daran zu denken, dass Anforderungen selbst wieder Parameter brauchen
können - etwa Anfang und Endposition für eine Textselektion.
Der Ausweg sind wieder erweiterbare Datentypen. Die Konvention ist in Oberon,
einen Prozedurtyp zu definieren, der auf folgender Deklaration basiert:
Handler*=PROCEDURE(frame:
Frame; VAR M: Msg);
mit
Msg*=RECORD
END;
und dann in im Modul Display zu definieren
Frame*=POINTER
TO FrameDesc;
FrameDesc*=RECORD
....
X*, Y*, W*, H*: INTEGER;
handler*: Handler;
...
END;
Der abstrakte Typ Msg enthält keine Information - ausser der Information,
die in der Typ-Deklaration selbst implizit enthalten ist. Als RECORD-Deklaration
ist sie erweiterbar. Wir können also zum Beispiel abgeleitete Typen
deklarieren - auch diese benötigt keine Information, wenn unsere Anwendung
sie nicht erfordert. Wir können etwa deklarieren:
PrintMsg*=RECORD
(Msg) END;
Die einzige Information, die wir benötigen ist, dass eine PrintMsg
eine wohldefinierte Anforderung ist, unterschieden von einer allgemeinen
abstrakten Anforderung vom Typ Msg. In der Ausführung, das heisst in
der Implementierung der handler-Prozedur, können diese unterschieden
werden mit einer Abfrage der Art
IF M IS PrintMsg
THEN
(* do everything
for printing*)
ELSIF M IS ... (*andere Msg-Typen
*)
END;
Wenn wir allerdings bevorzugen, beim Drucken auch mehrere Kopien zu
machen, so würden wir deklarieren
PrintMsg*=RECORD
(Msg)
copies: INTEGER
END;
Um diese abstrakte Konstruktion zu nutzen, ist ein bestimmtes Schema notwendig:
Wir definieren -je nach Typ und spezifischer Anwendung- eine Prozedur/Prozeduren,
etwa für einen Typ MyFrame
PROCEDURE MyHandler*(frame:
Frame; VAR M: Msg);
BEGIN
IF frame IS MyFrame
THEN
IF M
IS ...
ELSIF...
ELSE...
END;
ELSIF ... ELSE...
END;
END MyHandler;
Mit NEW erzeugen wir eine konkrete Instanz von MyFrame, etwa
VAR thisFrame:MyFrame;
...
NEW(thisFrame)
...
die wir dann initialisieren
thisFrame.handler:=MyHandler
... (* Initialisierung weiterer
Felder für MyFrame *)
Wenn wir ein Objekt obj als Ziel einer Anforderung, beschrieben durch ein
Nachricht M, identifiziert haben, wissen wir, dass
obj.handler
die für dieses Objekt angemessen Aweisungen zur Ausführung beinhaltet,
ohne dass wir das Objekt weiter inspizieren müssen. Mit
obj.handler(obj,
M);
lassen wir dann genau diese Anweisungen ausführen.
Das hier beschriebene Rezept ist flexibel und trägt allen Anforderungen
nach Erweiterbarkeit Rechnung. In der Regel werden wir eine Handler-Prozedur
für je einen Objekt-Typen haben. Das Rezept ist aber so flexibel, dass
es immer noch verschiedene Handler-Prozeduren für Objekte eines Typs
zulässt. Wir können damit flexible zur Laufzeit das Verhalten
eines Objekts ändern.
Oberon-2 lässt noch ein vereinfachtes Rezept zu, dass allerdings Einbussen
an Flexibilität mit sich bringt. Anstelle die Methoden an einzelne
Objekte zu binden, kann in Oberon-2 eine Methode an einen Objekt-Typen gebunden
werden. Dann übernimmt der Compiler die Aufgabe, den Methoden-Slot
zu initialisieren. Dazu dient der Zielparameter, den wir bereits bei der
Syntax der Prozedurdeklaration kennengelernt haben. Die Syntax war
Prozedur-Syntax:
PROCEDURE
[*] [Ziel] Prozedur-Name [formale Parameterliste];
Deklarations-Folge
[BEGIN
Anweisungs-Folge ]
END Prozedur-Name;
Für den Zielparameter gilt die Syntax
(
[VAR] Name: Typ )
Wenn ein Ziel angegeben ist, signalisiert dies, dass der beim
Ziel angegeben Typ eine Methode benötigt. Ein entsprechendes Feld wird
in diesem Fall nicht in der Typdeklaration angegeben, sondern vom Compiler
automatisch hinzugefügt. Das Feld hat den Namen der Prozedur. Jede
Variable vom Typ, der beim Zielparameter angegeben ist, wird automatisch
mit der entsprechende Methode verbunden. Die Prozedur ist jetzt als Methode
an den Typ gebunden, nicht an die einzeln Variable dieses Typs. Innerhalb
der Prozedur kann der Zielparameter benutzt werden wie jeder andere Parameter.
Mit typengebundenen Prozeduren ist auch Information über die Typ-Hierarchie
nutzbar. Diese kann durch "Aufwärts-Aufrufe" genutzt werden: ist x
eine Variable vom Typ T2 mit einer Methode handler, und T2 seinerseits eine
Erweiterung von Typ T1, so ruft x.handler die Methode entsprechend Typ T2,
jedoch x.handler^ die entsprechende Methode des Vorgängers Typ T1.
Der Compiler verwaltet die Typ-Hierarchie und kann dadurch diese Methode
finden. Eine übliche Konstruktion ist
PROCEDURE (obj:
T2) handler(obj:T1;VAR M:Msg);
BEGIN
IF M IS ..(* neue
Messagetypen der Erweiterung T2 *)
THEN
ELSE obj.handler^(obj,M)
(* Aufwärts-Aufruf für
Messagetypen,
die schon T1 kennt *)
END
END handler;
Im Gegensatz zur ersten Methode haben bei typengebundenen Prozeduren alle
Objekte eines Typs einheitliche Prozeduren. Wir können diese Prozeduren
nicht objekt-spezifisch variieren.
Literatur: J.L. Marais: Extensible Software Systems in Oberon. Journal of
Computational and Graphical Statistics, 5.3 (1996) 284-298
Einführung in die Programmiersprache Oberon. Kurs/Kap09.Text
gs (c) G. Sawitzki, StatLab Heidelberg
<http://statlab.uni-heidelberg.de/projects/oberon/kurs/>
Home
Up
Intro
Contents
Chapter
1
2
3
4
5
6
7
8
9
10
Design
Assert
Timing
EBNF
Report
Pas