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.

G. Sawitzki <gs@statlab.uni-heidelberg.de>



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