Forum: PC-Programmierung DesignFrage_


von Chefkoch (Gast)


Lesenswert?

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?

von Jack (Gast)


Lesenswert?

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.

von Chefkoch (Gast)


Lesenswert?

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)

von Jack (Gast)


Lesenswert?

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.

von Hans (Gast)


Lesenswert?

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.

von zer0 (Gast)


Lesenswert?

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?

von BobbyX (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.