Serialisierung mit vernünftigem physischen Design



  • Hi,

    wie setzt ihr Serialisierung sauber um?

    Probleme bei intrusiver Serialisierung

    Wenn ich eine Klasse schreibe, finde ich, dass die Serialisierung nicht zur Basisfunktionalität gehören soll, denn das sind oft viele, viele Zeilen Code und es stört die Übersicht und das Lesen der cpp-Datei enorm. Das wird umso schlimmer, wenn ich verschiedene Arten der Serialisierung anbieten möchte. Wenn ich meine Klasse beisielsweise als XML aber auch im Binärformat exportieren möchte, dann ist es unschön, wenn das alles in der cpp-Datei ist. Ich finde es ebenfalls unschön, wenn das Interface meiner Klasse Methoden enthält, die etwas exportieren sollen.

    Zudem finde ich es unpassend, dass meine Klasse neukompiliert werden soll, wenn ich in der Art des Exports/Imports etwas verändere. Und aus Wiederverwendungssicht möchte ich die Klasse gerne auch nutzen, ohne den Batzen an Export/Import-Overhead zu haben. Zusätzlich: von Legacy-Code, um alte Dateiformate etc. einzulesen, habe ich noch gar nicht angefangen.

    Letztlich gehört Serialisierung für mich auf eine Ebene, die sowohl meine Klasse als auch einen Schreiber/Leser fürs Exportformat (wie z.B. XML oder Datenbankformate) nutzt.

    Probleme bei ausgelagerter Serialisierung

    Somit ergibt Auslagern in eine andere Komponente (sprich cpp/h-Paar) für mich Sinn. Das Blöde ist nur, dass Serialisierung Zugriff auf die Interna der Klasse benötigt. Setter/Getter in meine zu serialisierende Klasse einzubauen mag ich nicht, weil das die Kapselung zerstört. Bliebe also erstmal nur eine friend-Beziehung, jedoch sollte man solche Beziehungen über KlassenDateigrenzen hinweg ja eher vermeiden, da man auf diese Weise ja einfach private Klassen/Funktionen mit demselben Namen nutzen kann, um Interna auszulesen (siehe auch "Large Scale C++" von Lakos).

    Zusätzlich hat man eine Namensabhängigkeit der zu serialisierenden Klasse zu den Export/Import-Klassen. Das lässt sich jedoch evtl. mit einem Attorney lösen. Den kann jedoch auch jeder missbrauchen.

    Ich finde sogar, dass man die Serialisierung in einen anderen namespace packen sollte. Wenn ich also einen Aufbau habe:

    * namespace1
    -- Klasse1
    -- Klasse2
    -- Klasse3

    , dann finde ich Serialisierung so sinnvoll:

    * namespace2
    -- Serialisierung für Klasse1
    -- Serialisierung für Klasse2
    -- Serialisierung für Klasse3

    , denn dann hat namespace1 keine Abhängigkeiten zu irgendwelchen Libs zum Schreiben/Lesen von XML, Datenbanken, Binrädateiformaten, Zugriffen auf die Zwischenablage des Betriebssystems (wenn man Serialisierung mit Mementos nutzen möchte) oder was auch immer.

    Wie löst ihr das in euren Projekten?

    Viele Grüße
    Eisflamme


  • Mod

    Wenn ich eine Klasse schreibe, finde ich, dass die Serialisierung nicht zur Basisfunktionalität gehören soll, denn das sind oft viele, viele Zeilen Code

    Das ist mit Boost.Serialization aber anders, oder irre ich mich?



  • Eisflamme schrieb:

    Bliebe also erstmal nur eine friend-Beziehung, jedoch sollte man solche Beziehungen über Klassengrenzen hinweg ja eher vermeiden, da man auf diese Weise ja einfach private Klassen/Funktionen mit demselben Namen nutzen kann, um Interna auszulesen (siehe auch "Large Scale C++" von Lakos).

    Ich halte von dem Argument nichts:
    Du hast die Klasse so designed, dass sie einfach richtig zu nutzen und schwer falsch zu benutzen ist (sinngemäß S.Meyers zitiert).
    Wenn ein Benutzer das Interface extra falsch nutzen will, dann ist da nicht dein Problem.
    Das dazu.
    Wie ich das jetzt konkret in C++ machen würde, weiss ich gerade nicht.
    Hab was ähnliches letztens in Java gemacht und da haben die Klassen ein Interface implementiert, was z.B. eine XML-Baum-Struktur erzeugt (in der Klasse also) und das eigentliche Erzeugen des XML-Files in einer eigenen Klasse, die dieses Interface übergeben bekommt.



  • Eisflamme schrieb:

    wie setzt ihr Serialisierung sauber um?

    Am besten gar nicht. Ernsthaft: Welches Problem versuchst du zu lösen?

    Du scheinst dich wie viele Programmierer in einer seltsamen "OOP"-Gedankenwelt festgefahren zu haben, wo Prinzipien nur als Vorwände herangezogen werden, um möglichst komplizierte Lösungen zu konstruieren. Damit kann man dann Kollegen oder Fremde in einem Forum beeindrucken.

    Außerdem wirfst du wild Buzzwords, grobe IT-Konzepte und konkrete Programmiersprachen-Features durcheinander. Dein eigentliches Problem hast du dabei gar nicht beschrieben, sondern nur die Lösungsansätze, die nicht funktioniert haben.

    Du bist selber auf einen Widerspruch gestoßen: Man kann Attribute nicht gleichzeitig verstecken und veröffentlichen. Anders formuliert: Implementierungsdetails und Schnittstellen sind tatsächlich unterschiedliche Dinge.

    Was soll hier warum an wen übertragen werden?



  • Das ist mit Boost.Serialization aber anders, oder irre ich mich?

    Ich hatte mir das mal angeschaut, es hat auf mich in erster Linie aber leider doch recht intrusiv gewirkt. Es gibt wohl auch eine andere Variante.

    Die Sache ist nur: Ob ich boost nutze oder es selbst mache, ist mir recht egal, ich möchte nur ein sauberes Design für diesen Nutzen haben.

    Jockelx:
    Okay, dein Gegenargument nehme ich an.

    Jedoch finde ich immer noch: Wenn ich eine Klasse habe, soll die nicht ihre Daten im XML-Format bereitstellen. Das ist ein Overhead, den ich häufig gar nicht möchte, daher empfinde ich das als ziemlich unsauber und hätte es gerne ausgelagert. Das wird für mich noch deutlicher, wenn ich die Daten z.B. für eine Datenbank auslagern möchte. Oder für 5 verschiedene Formate, dann müsste meine Klasse die alle unterstützen, das gehört da für mich nicht hin.



  • Hi,

    TyRoXx:
    Ne, das sind alles sehr pragmatische Gedanken. Ich refactore gerade ein (für mich) größeres Projekt und habe dabei einfach wirklich fest gestellt, dass es schwieriger zu warten und wiederzuverwenden ist, weil ich Serialisierung nicht sauber vom Rest getrennt habe. Und Abhängigkeiten erhöhen Link- und Compiletime, das habe ich auch fest gestellt. Die Unterstellung, meine Probleme kämen von blanker Theorie weise ich also zurück.

    Außerdem wirfst du wild Buzzwords, grobe IT-Konzepte und konkrete Programmiersprachen-Features durcheinander.

    Für mich ist davon nichts ein Buzzword, ich kann es dir gerne definieren, ich kann dir auch gerne konkret sagen, wo das bei mir im Projekt wichtig ist und was mein konkretes Problem damit ist. Alles konkret, pragmatisch und nicht aus der Luft gegriffen.

    Konkret ist mein Problem:
    Ich habe eine Klasse, die ich sowohl als XML speichern laden/lesen können möchte, um sie z.B. in Dateien zu stecken oder in der Registry abzulesen. Das soll vom Benutzer in der Form manipuliert und danach wieder importiert werden können, daher brauche ich das Format.

    Ich will auch undo/redo unterstützen, da diese Datenstruktur sich häufig ändert und der Benutzer das eben auch rückgängig machen können soll. Die Daten sind aber komplex und es hat sich herausgestellt, dass das Vorhalten der Mementos (States) in XML-Form 1. den Arbeitsspeicher zumüllt (wenn ich mir z.B. die letzten 10 Zustände merke) und 2. tatsächlich viel Performance in Anspruch nimmt. Schneller geht es mit einem Binärformat. Das kann der Benutzer natürlich nicht von Hand manipulieren, daher möchte ich es nicht für Dateien nutzen (und weil ich Updates einspiele und XML robuster ggü. Änderungen ist).

    Für den XML Im/Export nutze ich eine Lib. Ich möchte aber meine zu serialisierende Klasse nicht von der Lib abhängig machen, denn die benutze ich auch in anderen Projekten, bei denen es die XML-Anforderung nicht gibt.

    Weitere Details von Nöten?



  • klingt danach, als wäre boost erialisation das, was du willst. (also bis auf das nicht-intrusiv was eine ziemlich beknackte Anforderung ist. Serialisierung IST intrusiv.).



  • Eisflamme schrieb:

    Ich habe eine Klasse, die ich sowohl als XML speichern laden/lesen können möchte, um sie z.B. in Dateien zu stecken oder in der Registry abzulesen. Das soll vom Benutzer in der Form manipuliert und danach wieder importiert werden können, daher brauche ich das Format.

    [...]

    Für den XML Im/Export nutze ich eine Lib. Ich möchte aber meine zu serialisierende Klasse nicht von der Lib abhängig machen, denn die benutze ich auch in anderen Projekten, bei denen es die XML-Anforderung nicht gibt.

    Da haben wir Anforderung 1.

    Eisflamme schrieb:

    Ich will auch undo/redo unterstützen, da diese Datenstruktur sich häufig ändert und der Benutzer das eben auch rückgängig machen können soll.

    Das ist Anforderung 2.

    Ich sehe noch nicht, was diese beiden Anforderungen miteinander zu tun haben. Warum eine Serialisierung bei Anforderung 2 hilfreich ist, hast du noch nicht begründet. Warum nicht einfach Kopien der Objekte anlegen? Was ist das überhaupt für eine Klasse?



  • Hi,

    TyRoXx:
    die Klasse ist sehr abhängig vom Fachkontext, ich müsste viele Absätze schreiben, um das irgendwie zu erklären. Das Diskussionsniveau erscheint mir aber auch so sehr fruchtbar. 🙂

    Kopien der Objekte sind schwierig, da es sich um ganze Baumstrukturen handelt. Man könnte davon Kopien anlegen, jedoch sind dtor/copy-ctor/assignment-op an vielen Stellen private. Möglicherweise sollte ich das nochmal überdenken, ich meine jedoch, dafür gab es gute Gründe.

    otze:
    Kann ich so erstmal nicht nachvollziehen außer intrusiv schließt ein, dass man Interna offenlegt. Für mich bedeutet es aber nur, dass man neue Methoden in die Klasse einfügt, welche speziell für eine bestimmte Export/Importform notwendig sind. Nur wie gesagt: Dann wächst meine Klasse mit der Anzahl der Ausgabe/Einleseformate und das möchte ich ja nun nicht.

    Oder würdest du das anders gestalten? boost wirkte auf mich immer intrusiver als unbedingt nötig, vielleicht habe ich da aber eine Fehlauffassung im Kopf.

    Gruß
    E



  • Eisflamme schrieb:

    Kopien der Objekte sind schwierig, da es sich um ganze Baumstrukturen handelt.

    Bäume sind doch perfekt für undo/redo, wenn du das genaue Layout bestimmen kannst. Du kannst das vielleicht mit shared_ptr<TeilBaum const> "functional style" lösen. Das wäre dann logarithmischer Aufwand pro Änderung und ein moderater Speicher-Overhead.

    Eisflamme schrieb:

    Man könnte davon Kopien anlegen, jedoch sind dtor/copy-ctor/assignment-op an vielen Stellen private. Möglicherweise sollte ich das nochmal überdenken, ich meine jedoch, dafür gab es gute Gründe.

    Für mich impliziert Serialisierbarkeit, dass etwas kopierbar ist, aber vielleicht sehe nur ich das so 😕

    Eisflamme schrieb:

    Kann ich so erstmal nicht nachvollziehen außer intrusiv schließt ein, dass man Interna offenlegt. Für mich bedeutet es aber nur, dass man neue Methoden in die Klasse einfügt, welche speziell für eine bestimmte Export/Importform notwendig sind. Nur wie gesagt: Dann wächst meine Klasse mit der Anzahl der Ausgabe/Einleseformate und das möchte ich ja nun nicht.

    Diese Methoden können eine Art Reflection bereitstellen, auf der dann konkrete De/Serialisierer aufbauen. Boost Serialization macht das zum Beispiel so (nicht, dass ich dir die Bibliothek empfehlen würde, denn die ist Müll).



  • Eisflamme schrieb:

    Nur wie gesagt: Dann wächst meine Klasse mit der Anzahl der Ausgabe/Einleseformate und das möchte ich ja nun nicht.

    Und genau das hat boost doch perfekt gelöst. Du brauchst nur _eine_ Methode.

    Oder würdest du das anders gestalten? boost wirkte auf mich immer intrusiver als unbedingt nötig, vielleicht habe ich da aber eine Fehlauffassung im Kopf.

    nicht-intrusive serialisierung ist nicht möglich, wenn die member nicht alle public sind. Eine friend-funktion oder eine member funktion unterscheiden sich überhaupt nicht darin wie intrusiv sie sind(beide haben access zum allerheiligsten und beides muss immer geändert werden, sobald du die Datenstruktur anpasst).

    nebenbei unterstützt boost auch freie funktionen für die serialisierung, dass das geht wird schon dadurch deutlich, dass so ziemlich alles aus der standardbibliothek serialisiert werden kann.

    //edit es sei noch angemerkt dass sich "serialisierung" und "human readible"/"human writable" beinahe ausschließt. Serialisierung ist keine configdatei...



  • otze schrieb:

    //edit es sei noch angemerkt dass sich "serialisierung" und "human readible"/"human writable" beinahe ausschließt.

    Das kommt bloss auf das Ausgabeformat.
    .NET XmlSerializer und DataContractSerializer machen beide sehr schön lesbare XML Dokumente.
    Wie der XML Output von Boost.Serialization aussieht weiss ich nicht.

    Schwierig - was das Erzeugen von gut lesbaren/editierbaren Files angeht - wird es eigentlich nur wenn polymorphe Typen und/oder "mehrfach verlinkte" Objekte serialisiert werden müssen.



  • hustbaer schrieb:

    Wie der XML Output von Boost.Serialization aussieht weiss ich nicht.

    Boost unterstützt schon extra Knotennamen für XML, sodass man eigentlich allem einen Name zuweisen kann. Aber natürlich speichert die serialisierung selbst noch metainformationen die dann ein wenig reinpfuschen. Ich habe mir das XML-format nicht angesehen, aber das einfache textformat das sie unterstützen ist komplett unverständlich.



  • Hi,

    okay, ich muss wahrscheinlich noch etwas mehr Gedanken reinstecken. Mein Baum hat halt polymorph Knoten, eben weil es verschiedene Knotentypen gibt.

    Bäume sind doch perfekt für undo/redo, wenn du das genaue Layout bestimmen kannst. Du kannst das vielleicht mit shared_ptr<TeilBaum const> "functional style" lösen.

    Könntest du das ausführen? Das klingt erstmal interessant. Es ist gut möglich, dass ich etwas übersehe.

    Äste sind übrigens auch abhängig zueinander, was über signals gelöst wird, die bei einer Kopie wiederaufgebaut werden müssen. Das hatte ich nicht erwähnt, weil ich es gerade nicht im Kopf hatte, jedoch macht das Kopien auch teuer und das Kopieren einzelner Nodes oder Teilbäume nicht möglich. Zusätzlich sollen Dritte von Änderungen von Teilbäumen oder dem gesamten Baum erfahren, da ich den auch im UI darstelle und er von anderen UI-Elementen geändert werden können soll.

    Mein Baum ist im Prinzip ein Mengengerüst. Eine Menge kann Untermengen haben. Untermengen sind dabei disjunkt zueinander. Wenn ich in der Obermenge also etwas ändere, muss das auch in Untermengen bekannt werden, daher die Signals. Aber das ist eine andere Baustelle.

    Wie der XML Output von Boost.Serialization aussieht weiss ich nicht.

    Ich erinnere mich gelesen zu haben, dass das XML-Format von boost nicht dafür gedacht ist von Menschen manipuliert zu werden, das stammt, glaube ich, sogar von boost selbst. Ich finde die Quelle jedoch gerade nicht.

    Und genau das hat boost doch perfekt gelöst. Du brauchst nur _eine_ Methode.

    Dann wäre das gut. Ich erinnere mich daran, dass man Attribute mit & verknüpft und das sowohl const- als auch nicht-const geht und damit alles bestimmt. Zeiger auf andere Klassen waren aber wohl schwierig einzubauen, habe ich ebenfalls dunkel im Kopf.

    ---

    Eigentlich ist das für meine Frage hier ja auch egal. Das sind ja nur Formate, boost müsste mir sowieso anbieten noch weitere zu schaffen.

    nicht-intrusive serialisierung ist nicht möglich, wenn die member nicht alle public sind. Eine friend-funktion oder eine member funktion unterscheiden sich überhaupt nicht darin wie intrusiv sie sind(beide haben access zum allerheiligsten und beides muss immer geändert werden, sobald du die Datenstruktur anpasst).

    Okay, dann siehst du hier keinen signifikanten Unterschied zwischen friend/setter/getter/intrusiver Serialisierung? Eigentlich stört mich das auch nicht, wenn ich unterschiedliche Leser/Schreiber (je Format) dann wiederum nicht-intrusiv anschließen kann. Das müsste ja irgendwie gehen.

    Vielleicht ist meine Grundidee ja auch das Problem, die alles so extrem verkompliziert. Sie war:
    - Ich kann meine Klasse zu einem string bauen, den ich in eine Datei ablegen kann oder im Speicher halten für undo/redo, das ist praktisch
    - Kopien wären teuer, weil es signals zwischen Ästen gibt und die non-copyable machen, daher wollte ich die Serialisierbarkeit hier mitnutzen, um undo/redo zu unterstützen

    Das Thema bläht sich gerade leider etwas auf, pickt euch am besten raus, wozu euch gerade was einfällt. 🙂

    Danke schon mal!



  • Hi,

    keiner einen Gedanken, wie ich bei einem Baum, bei dem Zweige über signals verbunden sind, sauber serialisiere? Beim Kopieren kann ich signals ja nicht direkt kopieren, also muss ich die signals neu erzeugen. Fürs Rausschreiben in eine Datei brauche ich die signals wiederum nicht exportieren.

    Viele Grüße
    eisflamme


Anmelden zum Antworten