Hallo zusammen, ich habe eine Designfrage: >Situation: es gibt einige Instanzen von Objekten, von denen einige Felder parametriert werden können. Jedoch ist nicht jeder Wert gültig. Diese Parameter werden von außen durch eine serielle Schnittstelle geschrieben. Die Werte können einzeln oder sequentiell geschrieben werden. Ist bei einem seuentiellen Schrieb EINER der Werte außerhalb seines Bereiches, soll auch keiner der anderen Werte übernommen werden. >Ich könnte mir ein Design vorstellen, dass ein Notifierpattern verwendet: Der Befehlsparser geht die Nachricht durch und gibt jeden Wert an seine Zielinstanz und meldet die Instanz bei einem Notifier an. Die Instanz speichert den Wert erstmal in einem Shadowmember und meldet dem Notifier ob der Wert legal war. Hat der Notifier am Ende nur legale Werte ruft er eine bei allen angemeldeten Instanzen eine "execute" Methode auf, die den Shadowmember in einen "Executemember" überträgt. >Wäre das sinnvoll?
Chefkoch schrieb: >Wäre das sinnvoll? Ja, nein, vielleicht. Man müsste mehr wissen um beurteilen zu können ob das unter den vorhandenen Gegebenheiten eine gute oder nur eine brauchbare Lösung ist. Zum Laufen bekommt man sie. Mein Ansatz ist bei so etwas normalerweise ein anderer. Ich verwende Parameter-Objekte. Die Parameter-Objekte werden schrittweise aufgebaut. Z.B. immer wenn ein neuer Wert ankommt wird er ins Parameter-Objekt gepackt. Das Parameter-Objekt hat einen intern mitgeführten Gültigkeits-Zustand: incomplete, invalid, complete (valid). Mit jedem neu eingefügten Wert wird der Zustand neu evaluiert. Ist er incomplete wird weiter geparst. Bei invalid wird es weggeworfen und der Parser zurückgesetzt, bei complete wird der Parser zurückgesetzt und das Parameter-Objekt an das eigentliche Objekt übergeben. Meist instantiiere ich erst wenn ich das Parameter-Objekt fertig habe das eigentliche Objekt und übergebe das Parameter-Objekt im Konstruktor. Eine Precondition, die sicherstellt, dass das übergebene Parameter-Objekt im Zustand complete ist, ist eine gute Idee. Genau wie dass das Parameter-Objekt selber Änderungen an seinen Daten verhindert wenn es complete ist.
Hallo Jack, ja, inetressante Idee, man spart sich die Notifiertstruktur... Ich habe jetzt auch gedacht, dass meine Beschreibung zu dünn ist - das merkt man bein tippen oft nicht.. Ich denke, ein springender Punkt ist, dass die Instanzen unterschiedlich viele Werte haben und (hier springt der Punkt) die Werte nicht den selben Bereich haben. Im Sinne der Datenkapselung müsste die Gültigkeitsbewertung in die Instanz gebunden sein, die den Wert aufnimmt - es müsste also innerhalb der Instanz eine Art "Fragemechanismus" für jeden Wert vorhanden sein. Dazu kommt (der Punkt springt höher), dass bei sequentiellen Schrieben sich die Werte über mehrere Instanzen erstrecken können und das nicht unbedingt jeweils vollständig, da die Werte vom Parser (und damit die BusAdresszuweisung) nicht nach Objektzugehörigkeit abgearbeitet werden, sonden nach Funktion - z.B. >PWM_1 (Instanz 0) >PWM_2 (Instanz 1) >PWM_3 (Instanz 2) >>Mode_1 (Instanz 0) >>Mode_2 (Instanz 1) >>Mode_3 (Instanz 2)
Ich schmeiß jetzt mal so ein paar richtige Buzzwords in den Raum - aber aus den 1990igern :-) Also, so aus dem Bauch raus: > Ich denke, ein springender Punkt ist, dass die Instanzen unterschiedlich > viele Werte haben und (hier springt der Punkt) die Werte nicht den > selben Bereich haben. > Im Sinne der Datenkapselung müsste die Gültigkeitsbewertung in die > Instanz gebunden sein, die den Wert aufnimmt Gültigkeitsbewertung = Strategie. Strategie = Strategy Pattern. Das heißt, man parametrisiert wiederum die Parameter-Objekte mit Strategy-Objekten. Die Parameter-Objekte stellen dabei den Kontext für das Ausführen einer Strategie (einer Gültigkeitsbewertung). Denn egal was man macht, irgendwo (dann eben in Strategy Klassen) muss man die Gültigkeitskriterien implementieren. Wenn die Gültigkeitskriterien für X verarbeitende Objekte unterschiedlich sind, muss man X unterschiedliche Gültigkeitsbewertung implementieren. Der Code muss geschrieben werden, auch wenn er langweilig ist und nervt. An der Stelle könnte man auch wieder zurück gehen und die Parameter-Objekte weglassen und statt dessen die verarbeitenden Objekte mit Strategy-Objekten parametrisieren. Dann hat man die verarbeitenden Objekte allerdings wieder halbfertig beim Parsen in den Füßen und das Folgende macht weniger Spass: > Dazu kommt (der Punkt springt höher), dass bei sequentiellen Schrieben > sich die Werte über mehrere Instanzen erstrecken können und das nicht > unbedingt jeweils vollständig, Separate Parameter-Objekte (Instanzen) für jedes die Daten nachher verarbeitende Objekt. Zusätzlich zur Parametrisierung der Parameter-Objekte die Parameter-Objekte als Builder-Objekte auslegen. Alternativ, oder zusätzlich (jetzt habe ich mich beim Buzzword-Bingo warmgelaufen), die geparsten Werte per Visitor-Pattern durch die Parameter-Objekte "schießen". Parameter-Objekte die an einem Wert interessiert sind (Test mit einer Strategy :-)) nehmen ihn, die anderen ignorieren ihn. Ach ja: https://en.wikipedia.org/wiki/Design_Patterns#Patterns_by_Type Für sehr spezielle Anwendungen würde sich zum Füllen der Parameter-Objekte oder zum Beschreiben der Gültigkeitsbewertung eine embedded/internal DSL anbieten. Einfach mal nach "<Programmiersprache der Wahl> DSL" googeln.
Andere Idee: - Kopie der Instanz erzeugen - Nacheinander Parameter auf der Kopie ändern - validate()-Methode der Kopie fragen, ob alles passt - Falls OK: Alte Instanz und neue Instanz vertauschen, alte Instanz löschen - Falls nicht OK: Neue Instanz löschen Das ist in C++ ein gängiges Muster für Exception-Safety: Man führt alle Aktionen mit einer Kopie des Objekts durch und erst wenn alles geklappt hat (d.h. keine Exception flog), tauscht man Original und Kopie. Das Vertauschen an sich (std::swap) muss garantiert ohne Exception erfolgen, indem man z.B. nur Pointer vertauscht.
Hans schrieb: > Andere Idee: > - Kopie der Instanz erzeugen > - Nacheinander Parameter auf der Kopie ändern > - validate()-Methode der Kopie fragen, ob alles passt > - Falls OK: Alte Instanz und neue Instanz vertauschen, alte Instanz > löschen > - Falls nicht OK: Neue Instanz löschen > > Das ist in C++ ein gängiges Muster für Exception-Safety: Man führt alle > Aktionen mit einer Kopie des Objekts durch und erst wenn alles geklappt > hat (d.h. keine Exception flog), tauscht man Original und Kopie. Das > Vertauschen an sich (std::swap) muss garantiert ohne Exception erfolgen, > indem man z.B. nur Pointer vertauscht. Dem kann ich nur zustimmen. Allgemeiner Vorteil dabei ist auch, dass man statt nur Use-Case bezogen mit Shadow-Parametern usw. direkt in der Instanz herumzuhantieren, eine überall nutzbare validate() Funktion abfällt, die auch ohne großen Overhead komplexe logische Zusammenhänge im Objekt-State prüfen kann. Shadowing könnte bei größeren Objekten sinnvoll sein, indem man nur die Änderungen aufzeichnet und sonstige getter auf das Ursprungsobjekt durchschaltet. ca.
1 | struct IDaten { |
2 | // getter/setter für a und b usw.
|
3 | };
|
4 | |
5 | struct Daten : public IDaten { |
6 | int a, b; |
7 | // impl
|
8 | };
|
9 | |
10 | struct DatenShadow : public IDaten { |
11 | optional<int> a,b; |
12 | const IDaten* shadowed; |
13 | |
14 | int getA() { if(a) return *a; else return shadowed->getA(); } |
15 | void setA(int x) { a=x; } |
16 | };
|
Ist natürlich einiges an Text, aber das macht ja nichts, denn ihr nutzt doch alle UML-Diagramme um solcherlei Klassen automatisch zu generieren, oder nicht?
Du schreibst zuerst alle kommenden PArameter einer Sequenz in einen Container ( std::queue ). Dann überprüfst du die PArameter auf Gültigkeit. Wenn alle OK sind nimmst du sie von der Queue und verteils auf die enstsprchenden Objekte. Fertig.
Die Frage ist m.E. wer die Gültigkeitsprüfung durchführen kann. Wenn der Parser diese schon durchführen könnte, dann ist ein 2-pass-Ansatz auch in Ordnung. Die sequentielle eingeabe wird einmal durchgeparst und geprüft, und wenn erfolgreich werden die Daten ein zweites Mal geparst und gesetzt.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.