next up previous
Next: Sicherung und Restaurierung größerer Up: Persistenz Previous: Grundtechniken

Schnittstelle

In der Ulmer Oberon-Bibliothek wird die Eigenschaft der Persistenz durch das Modul PersistentObjects definiert, das den Typ PersistentObjects.Object exportiert, der eine Erweiterung von Services.Object ist. Persistenz wurde als primäre Abstraktion definiert, da

Der für PersistentObjects notwendige Verwaltungsaufwand besteht analog zu Erweiterungen von Services.Object aus der Registrierung der Typen (Services.CreateType), der Verknüpfung von Objekten mit Typen (Services.Init) und zusätzlich den angesprochenen Schnittstellenprozeduren. Um den Verwaltungsaufwand zu reduzieren, wird die Registrierung bei Services von PersistentObjects übernommen.

Für einen einfachen Objektdatentyp für ganze Zahlen sieht das beispielsweise folgendermaßen aus:[*]

Gegeben sei der Datentyp:

TYPE
   Integer = POINTER TO IntegerRec;
   IntegerRec = RECORD (PersistentDisciplines.ObjectRec) val: INTEGER END;

Als Basistyp wird hier PersistentDisciplines.Object verwendet, da damit persistente Disziplinen unterstützt werden - genauso wie typischerweise Disciplines.Object gegenüber Objects.Object vorzuziehen ist. Dann sind folgende Änderungen im Vergleich zu einer direkten Erweiterung von Services.Object notwendig:

1.
Im Initialisierungsteil des Moduls, das einen persistenten Typ definiert, ist statt

   Services.CreateType(type, "Integers.Integer", "");

nun

   PROCEDURE Init;
      VAR
         persif: PersistentObjects.Interface;
   BEGIN
      NEW(persif);
      persif.create := CreateInteger;
      persif.read := ReadInteger;
      persif.write := WriteInteger;
      persif.createAndRead := NIL;
      PersistentObjects.RegisterType(type,
         "Integers.Integer", "PersistentDisciplines.Object", persif);
   END Init;

aufzurufen.

2.
Im Konstruktor wird statt mit

   Services.Init(new, type);

die Verknüpfung mit

   PersistentObjects.Init(new, type);

hergestellt.

3.
Hinzu kommen die drei Schnittstellenprozeduren für PersistentObjects:

   PROCEDURE CreateInteger(VAR object: PersistentObjects.Object);
      VAR
         integer: Integer;
   BEGIN
      Create(integer, 0);
      object := integer;
   END CreateInteger;

   PROCEDURE ReadInteger(s: Streams.Stream;
                         object: PersistentObjects.Object) : BOOLEAN;
   BEGIN
      WITH object: Integer DO
         RETURN NetIO.ReadInteger(s, object.val)
      END;
   END ReadInteger;

   PROCEDURE WriteInteger(s: Streams.Stream;
                          object: PersistentObjects.Object) : BOOLEAN;
   BEGIN
      WITH object: Integer DO
         RETURN NetIO.WriteInteger(s, object.val)
      END;
   END WriteInteger;

Das Modul NetIO unterstützt die Ein- und Ausgabe aller Basistypen von Oberon in einer (für jeden Stream) adaptierbaren Weise, mit der ggf. Inkompatibilitäten zwischen verschiedenen Hardware-Architekturen ausgeglichen werden können.

Der Verwaltungsaufwand läßt sich bei Abstraktionen weiter reduzieren, die keine eigenen Daten unterhalten, die zu sichern und zu restaurieren sind. Dies trifft beispielsweise für eine Version von Collections zu, die persistent ist. Hier kann ganz einfach bei der Schnittstelle NIL übergeben werden und damit auf die Implementierung der Schnittstellenprozeduren verzichtet werden:

PersistentObjects.RegisterType(type,
   "Collections.Collection", "PersistentDisciplines.Object", NIL);

Auf solche Weise vorbereitete Objekte können dann mit PersistentObjects.Write in eine Sequenz von Bytes (über einen Stream) verwandelt oder von einem Stream mit PersistentObjects.Read wieder restauriert werden:

ok := PersistentObjects.Write(s, collection);

Beim Einlesen empfiehlt es sich, etwas vorsichtig zu sein, da nicht sichergestellt ist, daß genau das vorgefunden wird, was einmal früher korrekt abgelegt worden ist. Wenn beispielsweise

VAR collection: Collections.Collection;
(* ... *)
ok := PersistentObjects.Read(s, collection);

aufgerufen wird und das eingelesene Objekt keine Kollektion ist, dann hat PersistentObjects.Read keine Gelegenheit, FALSE zurückzuliefern - stattdessen gibt es einen Laufzeitfehler: type guard failure.

Um dies zu vermeiden, gibt es zwei Möglichkeiten:

1.
Einzulesendes Objekt als PersistentObjects.Object deklarieren und anschließend mit einem Typentest überprüfen:

   VAR
      collection: Collections.Collection;
      object: PersistentObjects.Object;
   (* ... *)
   IF PersistentObjects.Read(s, object) &
         (object IS Collections.Collection) THEN
      collection := object(Collections.Collection);
      (* ... *)
   END;

2.
Eleganter geht es mit PersistentObjects.GuardedRead:

   VAR
      collection: Collections.Collection;
      guard: Services.Type;
   (* ... *)
   Services.SeekType("Collections.Collection", guard);
   (* ... *)
   ok := PersistentObjects.GuardedRead(s, guard, collection);

Hier führt PersistentObjects selbst einen Typentest mit guard durch, bevor eine Zuweisung an collection stattfindet und liefert FALSE zurück, falls das eingelesene Objekt keine Erweiterung des guard ist.


next up previous
Next: Sicherung und Restaurierung größerer Up: Persistenz Previous: Grundtechniken
Andreas Borchert
2/2/1998