next up previous
Nächste Seite: Typerweiterungen Aufwärts: OO-Techniken in Oberon Vorherige Seite: Einführung

Das Modulkonzept von Oberon

Obwohl sich das Modulkonzept von Oberon von Modula-2 ableitet, weist es zwei signifikante Vereinfachungen auf.

So werden lokale Module von Oberon nicht mehr unterstützt. Dies ist einerseits bedauerlich, da lokale Module helfen, auch größere Implementierungen in übersichtliche Teile zu zerlegen.

Die interessantere Vereinfachung ergibt sich aus dem Verzicht auf nicht qualifizierte Importanweisungen. Modula-2 erlaubt es, fremde Module auf zwei verschiedene Weisen zu importieren. Mit FROM...IMPORT können einzelne Namen aus einem Modul ausgewählt und sichtbar gemacht werden. Bei IMPORT allein, ohne vorangehendes FROM, werden nur die Modulnamen selbst zugänglich. Namen aus diesem Modul müssen dann explizit mit einem Modulnamen qualifiziert werden:

MODULE SomeModule;

   FROM InOut IMPORT WriteString, WriteLn;
      (* nicht qualifizierter Import *)

   IMPORT RealInOut; (* qualifizierter Import *)

BEGIN
   WriteString("Hallo!"); WriteLn;
   RealInOut.WriteReal(1.0, 0);
END SomeModule.

Auf den ersten Blick sieht der qualifizierte Import umständlicher aus, da er zu längeren Namen zwingt. In der Praxis wird er in Modula-2 typischerweise nur verwendet, um Konflikte zu vermeiden, wenn zwei Module den gleichen Namen exportieren.

Trotzdem hat sich der Zwang zu Qualifikationen in Oberon bewährt. Einerseits relativiert sich das Problem der langen Namen rasch, wenn die Namen beim Entwurf einer Schnittstelle im Bewußtsein gewählt werden, daß der Modulname immer davor stehen muß. Zum anderen wird die Lesbarkeit entscheidend erhöht, da bei Verwendung von importierten Operationen der Modulname immer dabeisteht. Obiges Beispiel würde sich bei Verwendung der Ulmer Oberon-Bibliothek folgendermaßen lesen:

MODULE SomeModule;

   IMPORT Write;

BEGIN
   Write.String("Hallo!"); Write.Ln;
   Write.Real(1.0, 0);
END SomeModule.

Analog zu Modula-2 gibt es zu jedem Modul eine Schnittstelle, die ein separates Dokument darstellt. Die Schnittstelle ist dabei ein Auszug des zugehörigen Moduls. Im Gegensatz zu Modula-2 müssen also in der Implementierung alle Deklarationen aus der Schnittstelle wiederholt werden. Dies ist eine bemerkenswerte Vereinfachung, da das Modul damit nicht mehr abhängig ist von der Auswahl der exportierten Deklarationen. Zudem erleichtert es das Lesen, wenn alle lokalen Namen auch im gleichen Dokument deklariert werden.

Analog wurde das Konzept des abstrakten Datentyps aus Modula-2 sowohl vereinfacht als auch verallgemeinert. In Modula-2 werden abstrakte Datentypen, deren Repräsentierung verborgen bleiben soll, gemäß folgendem Beispiel deklariert:

DEFINITION MODULE Streams; (* Modula-2 Version *)

   TYPE
      File; (* abstrakter Datentyp *)

   PROCEDURE Open(VAR f: File; name: ARRAY OF CHAR);
   PROCEDURE PutChar(f: File; ch: CHAR);
   PROCEDURE GetChar(f: File; VAR ch: CHAR);
   PROCEDURE Close(f: File);

END Streams.

In der zugehörigen Implementierung muß File als Zeiger auf einen beliebigen Datentyp deklariert werden. In Oberon kann ein abstrakter Datentyp teilweise offengelegt werden gemäß dem Prinzip, daß die Schnittstelle einen Auszug der Implementierung offenlegen darf:

DEFINITION Streams; (* Oberon Version *)

   TYPE
      File = POINTER TO FileRec;
      FileRec =
         RECORD
            eof: BOOLEAN; 
            error: BOOLEAN;
         END;

   (* ... *)

END Streams.

Im Vergleich zu Modula-2 mag man zwar immer noch ahnen, daß sich hinter File ein abstrakter Datentyp mit privaten Komponenten verbirgt - jedoch läßt sich diese Tatsache nicht mehr auf syntaktische Weise aus der Schnittstelle schließen. Obwohl FileRec schon in der Schnittstelle beschrieben worden ist, hat die zugehörige Implementierung die Freiheit, FileRec um weitere Komponenten zu ergänzen:

MODULE Streams; (* Oberon Version *)

   TYPE
      File = POINTER TO FileRec;
      FileRec =
         RECORD
            (* oeffentliche Komponenten *)
            eof: BOOLEAN;
            error: BOOLEAN;
            (* private Komponenten *)
            fd: INTEGER;
         END;

   (* ... *)

END Streams.

Diese Freiheit fordert ihren Preis, da Klienten von Streams möglicherweise von der Speicherplatzgröße von FileRec abhängen. Obwohl es im obigen Beispiel nicht zweckmäßig ist, besteht die Freiheit, daß ein Klient folgendes deklariert:

   VAR
      filerecs: ARRAY 10 OF FileRec;

In so einem Fall kann der Übersetzer beim Klienten kaum ohne die tatsächliche Größe von FileRec auskommen. Beim Ulmer Oberon-Übersetzer wird das Problem dadurch gelöst, daß auch beim Übersetzen der Implementierung die Symboldatei geändert werden darf. Um die Abhängigkeiten zu reduzieren, geschieht dies normalerweise nur, wenn es unbedingt notwendig ist, d.h. der notwendige Speicherplatz sich vergrößert. In der Praxis hat es sich gezeigt, daß Neuübersetzungen aufgrund dieser Abhängigkeit ausgesprochen rar sind.

Es stellt sich natürlich die Frage, ob eine Zuweisung erlaubt ist, wenn Teile des Records privat sind. Dies kann zur Verletzung von Integritätsbedingungen führen. Wenn im obigen Beispiel ein geöffneter FileRec kopiert und geschlossen wird, dann ahnt das Original davon nichts. Im ersten Report über Oberon wurde diese Art der Zuweisung ausdrücklich verboten, im späteren Report verschwand jedoch diese Restriktion. Beim Ulmer Oberon-Übersetzer wurde die Restriktion beibehalten.


next up previous
Nächste Seite: Typerweiterungen Aufwärts: OO-Techniken in Oberon Vorherige Seite: Einführung
Andreas Borchert 2000-12-18