Klassendesign / Programmaufbau



  • Hallo Leute,

    als erstes, das hier wird länger.
    Also ich habe vor knapp 2 Jahren mal ein ursprünglich nur als kleines Logger Programm konzipiertes Programm in C geschrieben.
    Dies sollte verschiedene Gewichtsübungen mit Hanteln mitschreiben . "Leider" hat das alles gut geklappt und ist auch bei ein paar Kumpels gut angekommen, so dass immer mehr dazu gekommen ist, was zu einem ziemlich unübersichtlichen Spaghetti Code geführt hat. Nachträgliche Änderungen und / oder neue Übungen hinzufügen ist immer ein Graus. (Hab mir damals gedacht was ein großer Fehler war : "Hey, für paar Zeilen in ne Textdatei mitschreiben braucht es keinen Schönheitspreis gewinnen".)

    Nun befasse ich mich schon seit einiger Zeit mit C++, und da ich jetzt mit Klassen auch schon ne Zeit rumexperimentiere dachte ich mir, das wäre eigentlich ne passende "Übung" das alte Programm zu verwerfen und mit C++ neu zu machen.

    Die Grundfunktion Übungen mitzuschreiben schreit ja förmlich nach "NIMM eine Klasse" 🙂
    Jedoch will ich diesmal soweit es geht alles von Anfang an Richtig machen und darum wollte ich hier, bei den PROFIS mal nachfragen wie ich das am besten angehe, bzw. wie Ihr so manche Sachen machen würdet.
    Ich bin nur Hobby Programmierer habe also keine Erfahrung mit realen Projekten.

    Also ich umrschreibe mal grob wie das Programm so funktioniert (alte Version) und wieder funktioneren soll.

    Beim öffnen des Programms wird eine Txt Datei erstellt mit dem Format : "Training am WOCHENTAG XX-YY-ZZZZ # AA:BB UHR.txt"

    Dann kommt eine Menüabfrage nach den Trainingsarten " Gewicht oder Bodyweight".
    Anschließend kommt je nach Auswahl eine neue "Auswahlseite" mit der Auflistung der verschiednen hinterlegen Übungen.

    Nach auswählen der Übung folgen eigentlich nur noch "Dialaoge" . Der erste speichert das Gewicht auf der Hantel, der 2. ist dann eine ANzeige àla
    "ÜBUNGSNAME Satz : SATZNUMMER mit GEWICHT Wiederholungen : " hier wird dann eingegeben wieviele Wiederholungen geschafft wurden und das ganze dann in die TXT Datei geschrieben.... Weiter wird dann noch Mittelwerte , Gesamtwiederhlungen usw. am Ende berechnet.

    Also das Prinzip sollte klar sein.

    So ich dachte jetzt an die Klasse UEBUNG . Die würde dann als private Attribute eben einen Wiederholungszähler, eine Gewichtvariable usw haben.

    Dem Konstruktor hätte ich dann nen std::string Uebungsname übergeben.

    So erstes Problem : Die Dialaoge... sind das Methoden oder sollten das eher "normale" Funktionen sein ? Ist quasi "trainineren" (do Benutzerinterview while not Taste abbruch) eine Methode der Klasse Uebung? Gibt es eine Art Faustregel, wie man sich besser vorstellen kann was als Methode in eine Klasse gehört und was nicht ? Will jetzt nicht mit Gewalt alles reinpacken...

    So, zweites "Problem" : Im alten Programm sind die Übungen als "String" im Quellcode hinterlegt. Also das will ich nicht mehr, weil es soll auch mal schnell ne Übung hinzugefügt werden können, bzw. angepasst.
    Ich dachte ich lege ne "Config.dat" (oder.txt an, aber .dat sieht so schön Professionell aus 😉 ) an, in der die Übungen als Text stehen. Somit könnte ich diese einfach bei Programmstart in nen vector<string> einlesen und hätte es schon passend zur Übergabe an den Konstrukor ... ?? Was haltet Ihr davon ?

    Desweiteren ... Zu bestimmen Übungen gehören ja unterschiedliche Hanteltypen, die sich in Ihren Gewichtswerten unterscheiden. Dies muss die Klasse Übung allerdings wissen, um das "Gesamtgewicht" der Übungseinheit zu ermitteln. => Sollte dafür ein neuer Dialog gestartet werden, oder, wenn ich schon die Übungen aus einer Datei hole... evtl. die Stangenwerte auch dort hinterlege?? Man könnte evtl. dann gleich in eine Map einlesen, die Übungsnamen und zughörige Hantelstange enthält ? <string, int> ?

    Und abschließend noch eine How To Frage : Klasse aufteilen in hpp und cpp habe ich jetzt schon immer gemacht, ok. Aber macht es evtl. auch mal Sinn die Klassenmethoden auf mehrere cpp aufzuteilen ? Bei vielen Methoden die mal mehr als "Auto Hupen " enthalten, ist es schon mal viel Code auf eine Cpp ?

    So, wer bis zum Schluss durchgehalten hat mit lesen, dem Sei gesagt,ich freue mich über JEGLICHE anregung, und mir tun die Finger jetzt weh 🙂
    Noch einen schönen Feiertag und schon mal Danke für hoffentlich folgende Antworten.



  • Ps.: Ob ne Methode dann zu Klasse soll oder nicht wäre auch bei "Schreib in Datei" noch unklar...



  • Es fällt mir jetzt etwas schwer, hier eine sinnvolle Antwort zu geben... Du denkst irgendwie noch etwas zu geradlinig.

    Erster Grundsatz, Logik von der Darstellung immer trennen. Anscheinend willst du das ganze als Konsolenprogramm schreiben, ok. Ist ja auf jeden Fall egal. Aber achte trotzdem darauf, dass die Logik von der Darstellung getrennt ist. Die Dialoge, die die Informationen einer Übung aufnehmen, sollten von der Klasse Übung erstmal unabhängig sein. Oder besser gesagt, eine Klasse Übung sollte nichts über irgendwelche Dialoge oder Workflows wissen. Dann kannst du später eine GUI oder eine Webseite drüberbasteln, ohne an der Logik etwas zu ändern.

    Dann schaut das ganze so aus, als ob du zwei Klassen für die Übung hättest, eine Übungsdefinition oder Schablone, und eine Klasse, die eine durchgeführte Übungsinstanz darstellt.

    Und ja, es sieht auf jeden Fall so aus, als ob die Übungsdefinition etwas mehr oder weniger statisches wäre, wo man die Daten verschiedener Übungen aus einer Datei einlesen könnte. Hätte ich wohl auf jeden Fall so angefangen. Was hier später passieren kann wäre, dass du irgendwann eine Übung haben willst, die zusätzliche Logik benötigt. Dafür könnte es völlig unterschiedliche Lösungen geben, z.B. eine Basisklasse für die Übungen mit virtuelle Methoden für die Berechnung, oder immer noch eine einzige generische Klasse, die aber auch Formeln einlesen und auswerten kann usw...
    Ob die Klasse Übungsdefinition selber eine Methode hat, um die Daten einzulesen, oder ob das einlesen von einer anderen Klasse/Funktion erledigt wird, musst du entscheiden. Wär wohl besser, das Einlesen/Speichern extern zu machen, dann könntest du das wieder austauschen. Du schreibst jetzt vielleicht eine Methode, die die Daten aus einer Textdatei liest, aber später willst du das vielleicht erweitern und die Daten auch aus einer XML Datei lesen können. Oder aus einer Datenbank oder übers Netzwerk von einem Webservice bekommen. Das wäre auch etwas, was man hier trennen könnte.
    Hab mir schon gleich oben gedacht, dass man sowas wie Gewicht nicht jedesmal einzugeben braucht. Vielleicht kann man da eine Hantelverwaltung machen, die man unabhängig von den Übungen editieren kann, und bei der Übung wählt man nur das Trainingsgerät aus einer Liste?

    Die ganzen Überlegungen wie "string im Konstruktor" passen hier nicht rein. Du brauchst erstmal ein gröberes Konzept, solche Details stören. Wenn du an solche Details wie Parameter von einem Konstruktor denkst, wirst du sehr wahrscheinlich den Überblick verlieren.

    Den Code einer Klasse würde ich jetzt nicht in mehrere Cpp Dateien aufteilen, ist aber wohl mehr oder weniger Geschmackssache. Wir haben in der Arbeit viele cpp Dateien mit jeweils 20 000 Zeilen Code, das geht schon 😉 Nein, nimm dir kein Beispiel dran, das sind natürlich mit die übelsten Stellen bei uns.
    Aber so lang eine einzige Klasse nicht grad zehntausende Zeilen Code hat, würde ich die nicht aufteilen. Und wenn eine Klasse so viel Code hat, dann hast du definitiv was falsch gemacht. Ich mach das eher umgekehrt, ich hab in einer Cpp oft viele interne Hilfsklassen, die ich nie in einer Headerdatei deklariere. Eine Cpp repräsentiert für mich nicht unbedingt eine Klasse, sondern eher ein "Modul" mit einer gewissen Funktionalität und einer öffentlichen Schnittstelle. Das meiste andere sind Implementierungsdetails und bleiben in der cpp.



  • Erstmal Danke für die Antwort. Hat mir auf jeden Fall schon mal weitergeholfen. Ich stelle das "Projekt" noch etwas hinten an, ich glaube ich bin noch nicht soweit dies Optimal zu lösen. Habe bei Kloassen noch keine Vererbung ableitung usw. in meinem Buch gemacht. Denke das dies hierfür aber fast nötig ist.

    zu

    Erster Grundsatz, Logik von der Darstellung immer trennen

    Es fätt mir schwer dies für mein "Prgramm" umzusetzten, bzw. ich kann es mir nicht vorstellen wie ich das richtig machen soll.
    Z.B.:

    std::cout << "Bankdrücken Satz " << Satzctr << " mit WH : " 
    //hier würde ich in die Membervariable "xyhaumichbalu" einlesen
    
    // dann käme noch die Abfrage ob eingabe Korrekt und wieviel Pause gemacht werden soll
    

    Wie könnte ich das dann vernünftig trennen ?

    Generell: Gibt es ein Buch oder dergleichen , quasi Design Patterns für anfänger , evtl. auf Deutsch oder etwas anderes empfehlenswertes? Beispiele die mal über Klasse Auto.hupen oder Ampel.einschalten(gelb) hinausgehen?



  • beg_offl schrieb:

    Habe bei Kloassen noch keine Vererbung ableitung usw. in meinem Buch gemacht. Denke das dies hierfür aber fast nötig ist.

    Nee, seh hier jetzt keine Ableitung. Das Projekt ist weitgehend trivial, das kann man fast genauso wie in C machen (wenn man das in C vernünftig machen würde).
    Und Ableitung wird in C++ eh nicht so oft verwendet. Hier schreit irgendwie gar nichts nach Ableitung.

    beg_offl schrieb:

    Wie könnte ich das dann vernünftig trennen ?

    Sagen wir, du hast eine Klasse ExerciseDefinition. Und es gibt einen gewissen Workflow, einen Satz von Parametern, den der Benutzer eingeben muss. Falsch wäre, das in der Klasse zu implementieren. Besser wäre es, wenn die Klasse sowas wie eine Liste von Steps hat.
    Das Abfragen der Benutzereingaben wäre dann generisch und unabhängig davon. Du hättest dann in deinem Benutzerinterface eine Funktion zum Ausführen des Workflows und gibst eine ExerciseDefinition als Parameter rein. Da drin werden die möglichen Worflowsteps aus dem Parameter geholt und deine Benutzerschnittstelle weiß, wie sie Eingaben holt. Wie du weiter machst, hängt davon ab, was du sonst alles brauchst. Was heißt z.B. prüfen, ob die Eingabe korrekt ist? Da muss man sich noch überlegen, wie dein Domänenmodell ausschaut und wo das reinpassen könnte. Könnte man z.B. in den "Step" reinpacken. Ich weiß nicht genau, wie das alles bei dir ausschaut, aber ich könnte mir irgendsowas vorstellen:

    void executeExercise(const ExerciseDefinition& ex)
    {
      vector<ExerciseStep> steps = ex.steps();
      for (const ExerciseStep& step: steps)
      {
        cout << step.description();
        vector<StepParameter> params = step.requiredParameters();
        for (const StepParameter& param: params)
        {
           string paramValue;
           cout << param.description();
           cin >> paramValue;
           if (!param.validateInput(paramValue))
             cout << "Ungültiger Wert!";
        }
      }
    }
    

    Nur als Beispiel. Du hast irgendwelche Strukturen, die das Modell und die Logik beschreiben und implementieren, und eine Benutzerinteraktionsschicht, die außerhalb davon implementiert ist und auf das Modell zugreift. Wie dein Modell konkret aussieht ist grad die spannende Frage beim Modellieren.
    Wenn du das dann auf ein richiges GUI Framework wie Qt portierst, brauchst du nichts an dem Modell zu ändern. Du hättest dann nur eine andere executeExercise Funktion, die statt mit cout und cin zu arbeiten entsprechende Eingabefelder erstellt.

    Es gibt viele Bücher über Design Patterns. Das Originalbuch von den GoF ist ganz gut. Ich glaub, das gibts auch auf deutsch, bin mir aber nicht mehr sicher. Hab auch schon paar andere Bücher auf deutsch durchgeblättert, die ganz brauchbar ausgeschaut haben.
    Dann gibts Architekturpatterns, die größere Blöcke beschreiben. Gibt z.B. eine Buchreihe Pattern Oriented Software Architecture. Fand ich jetzt nicht so bahnbrechend weil ich das meiste eh schon kannte, aber viel besser Bücher kenn ich jetzt spontan auch nicht.
    Gibt grundsätzlich schon recht viele Bücher über Softwaredesign und Architektur. Viele basieren auf den oben erwähnten Büchern und versuchen das zusammenzufassen. Dann gibts auch modernere Bücher, die versuchen Buzzwords wie agil oder scrum einzubinden. Hatte bisher keine Zeit, sowas zu lesen.
    Und ich denke, man kann das am besten verstehen, wenn man selber schon ein gewisses Verständnis dafür entwickelt hat. Ich fand das am Anfang alles trocken und langweilig und wollte nur programmieren und hab auch nicht eingesehen, wo und wie ich das alles einsetzen könnte. Bei mir hats erst irgendwann im Studium Klick gemacht, davor hab ich jahrelang einfach nur Spaghetticode programmiert.



  • @Mechanics : Vielen Dank. Wie gesagt, ich muss mich noch ein bisschen mehr einlesen usw. aber Deine Anregungen haben mir schon weitergeholfen. Werde das Thema zur gegebenen Zeit nochmal aufgreifen 🙂


Anmelden zum Antworten