Klassendesign zur Repräsentation strukturierter Eingabedaten



  • Hallo, ich habe bisher immer interessiert hier mitgelesen und habe nun auch mal was zu fragen :). Folgendes: Mein Programm bekommt von außen hierarchisch strukturierte Daten aus einer Datei (XML oder JSON), die in etwa wie folgt aussehen könnten (komplexere Schachtlungen sind durchaus möglich):

    {
        ...
    
        "obj1": {
            "name": "apple",
            "date": "22.12.1998", 
            "n": 47, 
            "b": "true",
            "f": 4.2,
            "pair": [10, 10]
        },
    
       "obj2": {
            "name": "pear",
            "date": "12.02.1978", 
            "n": 11, 
            "b": "false",
            "f": 3.1415,
            "pair": [1, 2]
        },
    
        ... 
    }
    

    Nun kann ich mir das sehr einfach mit einem boost::property_tree einlesen und durch die Daten iterieren. Allerdings ist der property_tree als grundlegende Datenstruktur nicht besonders geeignet, da das auslesen nicht-trivialer Typen einen ziemlichen Overhead an Code verursacht. Ich will nachher die Daten lesen können und will auch nicht jedes Mal so etwas wie make_date(obj1.get<string>("date")) schreiben, wenn ich die Daten in einer bestimmten Form verwerten will. Mir aber um einen Zweig einen Wrapper zu basteln, der zig Methoden bereitstellt um die Daten zu lesen und umzuwandeln (z.B. Einheiten extern "cm" vs. intern "m" o.ä.) finde ich aber auch ziemlich wenig elegant. Zumal ich gerne die Konvertierung in meinen jeweiligen Datentyp gerne nur einmal vornehmen würde, da manche Daten ziemlich oft im Laufe der Berechnungen adressiert werden müssen. Meine Daten sind in gewisser Weise wie ein POD für den Benutzer, aber mit ein wenig Aufwand hinter der Fassade.

    Ich habe mir also überlegt, dass ich die Struktur aus der Datei nachbilde mit Hilfsobjekten vom Typ property<T>, die einfach nur einen Konstruktor haben, der weiß wie man die Daten aus dem Baum extrahiert und sonst nur den operator () bereitstellen.

    Also (semi-pseudocode):

    class object
    {
      template<typename T>
      class property {
          T value_;
          property();
          property(property const& other);
          property& operator=(property const&);
        public:
          // Konstruktor kann für verschiedene T spezialisiert werden
          // im einfachsten Fall weist er value den Wert pt.get<T>(name) zu
          property(string const& name, property_tree const& pt);
          T const& operator () () const { return value_; }
      };
    public:
       property<string> name;
       property<mydate> date;
       property<int>    n;
       property<bool>   b;
       property<double> f;
       property<pair<int,int>> pair;
    
       object(property_tree const& tree) :
          name("name", tree),
          date("date", tree),
          n("n", tree),
          b("b", tree),
          f("f", tree),
          pair("pair", tree)
       {
          // evtl. checks, die schauen, ob die Daten untereinander stimmig sind
       }
    
    };
    

    Das hat nun den Vorteil, dass ich mit z.B. den Ast "obj1" aus dem Baum schnappen und in ein object stecken kann. Dann kann ich recht einfach damit arbeiten.

    object obj1(pt, "obj1");
    int n = obj1.n();
    // usw.
    

    Meine Fragen:

    1. Hat das oben dargestellte Konzept einen gravierenden Haken? Was könnte man besser lösen? Und wie lege ich Objekte vom Typ object am besten an um mögliche Datenfehler (Struktur des Baums bzw. einzelner Datenfelder) abzufangen? Mit einer Factory? Ich finds eigentlich ganz schick, aber man ist ja gerne betriebsblind was seine eigenen Kreationen betrifft :).

    2. Wie schaut es aus, wenn ich nachher potentiell die Daten für den Benutzer editierbar machen will? Lasse ich den am besten nur auf dem Property-Tree arbeiten und behalte die Struktur oben als read-only um sie nicht unnötig aufzublähen? Sozusagen als Hilfsklasse für den Teil des Programms, der mit den Daten arbeiten muss... Es sind in meinem Fall nicht so wahnsinnig viele Daten pro Objekt, dass es sich ggü. dem Neuaufbau aus einem property_tree lohnen würde die Klasse oben um setter aufzublähen.



  • Keine Meinungen?



  • Das kommt darauf an was Du mit den Daten machst?

    Ich persoenlich finde, dass halten von Daten in solchen Strukturen (sei es JSON oder XML) nicht wirklich effizient.

    Deswegen versuche ich persoenlich solche Strukturen so frueh wie moeglich in eine richtige C++ Klassenstruktur zu ueberfuehren.
    Das bedeutet die Daten existieren nur sehr temporaer in einer solchen Struktur und werden dann schnellst moeglich ueberfuehrt.

    Das macht aber nur natuerlich nur Sinn, wenn es keine "fluechtigen" Informationen sind.

    Wenn Du z.b. JSON/XML bekommst und dieses gleich wieder an alle moeglichen Leute ausliefern musst macht das hin und her konvertieren nicht mehr allzuviel Sinn.

    z.B. Dein "object" Du scheinst die Daten aus dem Prop.Tree zu extrahieren. warum ueberfuehrst Du sie nicht wieder in eine "richtige" Datenstruktur, also einen richtigen String, int bool, double etc.

    Wofuer benoetigst Du denn eigentlich, dass "property"-Objekt nachdem Du die Daten schon aus dem tree extrahiert hast?



  • Ja, genau darum geht es mir: um eine schicke Lösung, die mich weg vom Property-Tree führt (der zwar zum einlesen schick ist, aber danach nur umständlich zu handhaben) und hin zu Datentypen die ich direkt ohne Konvertierung und Checks an vielen Stellen im Programm verwenden kann. Ich lese die Daten im Regelfall nur ein, wobei es potentiell irgendwann auch passieren kann, dass ich dem User die Möglichkeit geben muss eigene Datensätze anzulegen. Die Daten sind in der Lösung oben auch in einem richtigen Datentyp, nur halt mit dem property-Proxy weggekapselt, damit ich mir spare zusätzlich einen 'getter' zu realisieren und damit ich die Zuweisung aus dem Tree an den konkreten Datentyp pro Typ nur einmal implementieren muss (beim spezialisieren des Konstruktors). Das sieht in einem Beispiel mit 3-4 Membern vielleicht wie over-engineering aus, aber ich kann da potentiell 30-40 Member pro Datensatz haben in dem konkreten Fall, die ich leider nicht sinnvoll weiter splitten kann. Ich muss aber zusätzlich sicherstellen, dass die Konvertierungen passen und ich keinen Member vergessen kann wenn mal was erweitert wird. Das stelle ich dadurch sicher, dass ich den Programmierer (also z.B. mich) zwinge den nicht-trivialen Konstruktor aufzurufen.



  • Ruvi schrieb:

    z.B. Dein "object" Du scheinst die Daten aus dem Prop.Tree zu extrahieren. warum ueberfuehrst Du sie nicht wieder in eine "richtige" Datenstruktur, also einen richtigen String, int bool, double etc.

    Wofuer benoetigst Du denn eigentlich, dass "property"-Objekt nachdem Du die Daten schon aus dem tree extrahiert hast?

    Hab gerade erst gesehen, dass Du das ergänzt hast. Also das Property steuert mir den Zugriff, erspart mir also Getter (und ggf. Setter) für die zig Member einzeln zu schreiben. Außerdem erspart es mir etwas Code bei der Initialisierung, weil ich das Extrahieren der Daten aus dem Tree pro Datentyp nur einmal im Konstruktor implementieren muss. Wie gesagt, ich spreche hier von 30-40 individuellen Membern die alle einen eindeutigen Namen haben (also leider nicht zu einem Array o.ä. zusammengefasst werden können). Ich will einfach nicht bloß die Holzhammermethode sondern eine Lösung wo schon der Compiler meckert wenn ich einen Member zu initialisieren vergesse, wo ich mal Breakpoints oder Logging-Ausgaben in die Zugriffsroutinen setzen kann und wo ich mir sicher sein kann, dass der Benutzer mir read-only Members nicht verändert, allerdings mit dem Komfort eines POD-Typen. Nur würde ich gerne wissen ob das was ich will nicht auch eleganter als oben aufgeführt geht.


Anmelden zum Antworten