Selbst wenn die Programmiersprache Typentests wie z.B. Oberon zur Verfügung stellt, ist dies noch lange nicht so allgemein wie die Einführung von Typen (oder Klassen) als 1st class objects. So ist es beispielsweise in Oberon nicht möglich herauszufinden, ob der Typ eines Objekts eine Erweiterung des Typs eines anderen Objekts ist. Smalltalk hat zuerst Metaklassen eingeführt und andere Sprachen wie z.B. Self sehen überhaupt keinen Unterschied mehr zwischen Klassen und Objekten.5.6
Wenn die Programmiersprache selbst nicht die notwendige Unterstützung liefert, ist es prinzipiell kein Problem, sie im Rahmen einer Bibliothek nachzuholen, wobei dann allerdings Konventionen entstehen, die konsequent einzuhalten sind, um diese Möglichkeiten umfassend ausnutzen zu können. Um diese Konventionen nicht auf alle zu kreierenden Objekte auszudehnen, werden in der Ulmer Oberon-Bibliothek Typen als 1st class objects nur für Erweiterungen von Services.Object5.7angeboten, das selbst eine Erweiterung von Disciplines.Object ist.
Und so sehen die Konventionen in der Ulmer Oberon-Bibliothek aus:
VAR type: Services.Type; (* globale private Variable *) (* ... *) (* im Initialisierungsteil von Collections *) Services.CreateType(type, "Collections.Collection", "");
Bei Services.CreateType wird als dritter Parameter der Name des Basistyps angegeben, wobei statt Services.Object einfach eine leere Zeichenkette angegeben wird. Dies würde bei einer Erweiterung von Collections.Collection wie z.B. bei LinearLists anders aussehen:
VAR type: Services.Type; (* globale private Variable *) (* ... *) (* im Initialisierungsteil von LinearLists *) Services.CreateType(type, "LinearLists.List", "Collections.Collection");
Auf diese Weise lernt Services nach und nach die gesamte Typhierarchie, die von Services.Object abgeleitet ist, kennen. Natürlich ist die korrekte Aufrufreihenfolge wichtig, da (Services darauf besteht, daß der Basistyp bereits registriert ist). Solange allerdings keine Zyklen bei der Importrelation eingebaut worden sind, wird dies durch die Initialisierungsreihenfolge gewährleistet.
Services.Init(list, type); (* zuerst mit dem Typ und dann ... *) Collections.Init(list, if, caps); (* ... mit der Schnittstelle verknuepfen *)
VAR ctype: Services.Type; (* lokale Variable von Collections.Init *) (* ... *) Services.GetType(collection, ctype); ASSERT((ctype # NIL) & Services.IsExtensionOf(ctype, type));
Wenn all diese Vorbereitung stattgefunden haben, ist es möglich, den Typ eines jeden Objekts zu ermitteln und als Objekt zu behandeln, wenn der Typ des Objekts eines Erweiterung von Services.Object ist. Da Services.Type eine Erweiterung von Disciplines.Object ist, besteht auch insbesondere die Möglichkeit, Disziplinen an Typen anzuheften.
Dieser Mechanismus ist von besonderem Interesse für sekundäre Abstraktionen, die nicht von dem Modul unterstützt werden, das für die Implementierung der primären Abstraktion zuständig ist, und für die andererseits auch keine allgemein gültige Implementierung existiert. Stattdessen wird die Implementierung der sekundären Abstraktion von beliebig vielen hinzukommenden Modulen realisiert, die den entsprechenden Dienst jeweils für bestimmte Typen und deren Ableitungen anbieten. Dies wird von Services direkt unterstützt und daher ergab sich auch der Name dieses Moduls. Ein mögliches Szenario hierfür illustriert die Abbildung 5.5:
Genauso wie bei primären Abstraktionen bieten sich auch hier Schnittstellen-Records an (siehe §2.7), die von SSSs öffentlich definiert und über eine Support-Operation mit dem jeweiligen Typ verbunden werden. Als Beispiel (Importhierarchie in Abbildung 5.6) sei hier eine Variante von PrintableObjects gegeben, die auf Services basiert:5.8
DEFINITION PrintableObjects; IMPORT Services, Streams; (* secondary abstraction for printable objects which is usually to be supported by type-dependent modules *) TYPE PrintProc = PROCEDURE (s: Streams.Stream; object: Services.Object); PROCEDURE Support(for: Services.Type; print: PrintProc); (* supply print method for all instances of `for' and its extensions *) PROCEDURE SetDefault(s: Streams.Stream; default: ARRAY OF CHAR); (* sets the per-stream default output text for objects which do not have an associated print method *) PROCEDURE Print(s: Streams.Stream; object: Services.Object); (* call the print method of `object', or, if not present, the per-stream default text, or, if not present, a question mark *) END PrintableObjects.
In Support wird die Schnittstelle dann an den Typ über eine Disziplin geheftet:
PROCEDURE Support(for: Services.Type; print: PrintProc); (* supply print method for all instances of `for' and its extensions *) VAR typeDisc: TypeDiscipline; BEGIN Services.Define(for, service, NIL); NEW(typeDisc); typeDisc.id := typeDiscID; typeDisc.print := print; Disciplines.Add(for, typeDisc); END Support;
Bei der Anwendung wird dann jeweils nach dem unterstützten Basistyp des übergebenen Objekts gesucht und dort die Schnittstelle der Disziplin wieder entnommen:
PROCEDURE Print(s: Streams.Stream; object: Services.Object); (* call the print method of `object', or, if not present, the per-stream default text, or, if not present, a question mark *) VAR baseType: Services.Type; typeDisc: TypeDiscipline; streamDisc: StreamDiscipline; BEGIN Services.GetSupportedBaseType(object, service, baseType); IF (baseType # NIL) & Disciplines.Seek(baseType, typeDiscID, typeDisc) THEN typeDisc.print(s, object); ELSIF Disciplines.Seek(s, streamDiscID, streamDisc) THEN Write.StringS(s, streamDisc.defaultText); ELSE Write.StringS(s, defaultText); END; END Print;
Ein Diensteanbieter wie beispielsweise PrintableCollections muß dann nur noch seine Implementierung mittels PrintableObjects.Support registrieren:
MODULE PrintableCollections; (* implements PrintableObjects for Collections *) IMPORT Collections, Disciplines, PrintableObjects, Services, Streams, Write; PROCEDURE PrintCollection(s: Streams.Stream; object: Services.Object); VAR member: Disciplines.Object; first: BOOLEAN; BEGIN WITH object: Collections.Collection DO Write.CharS(s, "("); Collections.First(object); first := TRUE; WHILE Collections.Next(object, member) DO IF first THEN first := FALSE; ELSE Write.StringS(s, ", "); END; IF member IS Services.Object THEN PrintableObjects.Print(s, member(Services.Object)); ELSE Write.CharS(s, "?"); END; END; Write.StringS(s, ")"); END; END PrintCollection; PROCEDURE Init; VAR type: Services.Type; BEGIN Services.SeekType("Collections.Collection", type); PrintableObjects.Support(type, PrintCollection); END Init; BEGIN Init; END PrintableCollections.