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.
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.