next up previous
Nächste Seite: Sicherung und Restaurierung größerer Aufwärts: Persistenz Vorherige Seite: 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:6.4

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
Nächste Seite: Sicherung und Restaurierung größerer Aufwärts: Persistenz Vorherige Seite: Grundtechniken
Andreas Borchert 2000-12-18