Die vorhandene Schnittstelle kann auch bequem im Falle beliebiger dynamischer Datenstrukturen verwendet werden, da die Schnittstellenprozeduren zum Schreiben und Lesen natürlich PersistentObjects.Write und PersistentObjects.Read rekursiv aufrufen dürfen.
Falls Zeiger möglicherweise NIL sind, können auch PersistentObjects.WriteObjectOrNIL und PersistentObjects.ReadObjectOrNIL verwendet werden. So könnte beispielsweise eine Sicherungsprozedur für einen binären Baum aussehen:
PROCEDURE WriteTree(s: Streams.Stream; tree: PersistentObjects.Object) : BOOLEAN; BEGIN WITH tree: Tree DO RETURN PersistentObjects.WriteObject(s, tree.object) & PersistentObjects.WriteObjectOrNIL(s, tree.left) & PersistentObjects.WriteObjectOrNIL(s, tree.right) END; END WriteTree;
Abbildung 6.2 zeigt das Schema eines entsprechend serialisierten binären Baumes.
Die Rekursion führt natürlich bei zyklischen Datenstrukturen zu Problemen, die mit dem Modul LinearizedStructures elegant vermieden werden können. LinearizedStructures verwaltet über eine private Disziplin bei dem Stream die Liste der bereits gesicherten (bzw. eingelesenen) Objekte und gibt entsprechend bei Wiederholungen Verweise aus und kann sie beim Einlesen wieder interpretieren. Entsprechend ist es beispielsweise möglich, einen zweiseitig verketteten Ring zu sichern (siehe auch Abbildung 6.3):
PROCEDURE WriteRing(s: Streams.Stream; ring: PersistentObjects.Object) : BOOLEAN; BEGIN WITH ring: Ring DO RETURN LinearizedStructures.WriteObject(s, ring.object) & LinearizedStructures.WriteObject(s, ring.prev) & LinearizedStructures.WriteObject(s, ring.next) END WriteRing;
Dabei sind LinearizedStructures.WriteObject und LinearizedStructures.ReadObject analog zu PersistentObjects.WriteObjectOrNIL und PersistentObjects.ReadObjectOrNIL tolerant gegenüber NIL-Zeigern.
Manchmal ist es aber auch einfacher, eine gesamte dynamische Datenstruktur in einer Prozedur zu sichern (bzw. zu restaurieren), wenn die beteiligten Objekttypen privat definiert werden. Dies spart den Aufwand für mehrere persistente Datentypen. Als Beispiel sei hier die Sicherungsprozedur von LinearLists aus dem Beispiel Collections-166.5gegeben:
CONST objectSY = "O"; endSY = "E"; (* encoding for persistent representation *) (* ... *) PROCEDURE WriteList(s: Streams.Stream; list: PersistentObjects.Object) : BOOLEAN; VAR member: Linkable; BEGIN WITH list: List DO member := list.head; WHILE member # NIL DO IF member.object IS PersistentObjects.Object THEN IF ~NetIO.WriteChar(s, objectSY) OR ~PersistentObjects.Write(s, member.object(PersistentObjects.Object)) THEN RETURN FALSE END; END; member := member.next; END; END; RETURN NetIO.WriteChar(s, endSY) END WriteList;
Wenn die Anzahl der gesicherten Objekte in so einem Fall variabel ist, empfiehlt es sich, die Zahl der Objekte oder - wie im Beispiel - spezielle Symbole auszugeben, die das korrekte Einlesen nachher ermöglichen. In diesem Beispiel wurde auf diese Weise vermieden, den internen Datentyp Linkable als persistentes Objekt zu definieren. Außerdem ist diese Lösung effizienter, da sie ohne Rekursion auskommt.