Hallo zusammen,
Ich habe hier eine Bildungslücke und tu mir schwer diese sauber zu
vertuschen ;)
Es geht darum, dass ich ein Programm in C++ schreibe, welches über eine
Serielle-Schnittstelle mit meiner Platine kommuniziert.
Nun habe ich mir ein "Objekt" geschrieben, welches quasi die serielle
Schnittstelle darstellt und Pakete/serille Kommandos verwaltet (in eine
Liste aufnimmt und nacheinander absendet). Zudem empfängt dieses
Schnittstellen-Objekt auch Daten/Antworten und ordnet diese den
Paketen/Kommandos wieder zu, um festzustellen, ob die Bearbeitung des
Kommandos erfolgreich war, oder um Daten zu empfangen.
Nun sind aktuell meine Paket-Objekte noch etwas sehr dummy-artig
programmiert. Die Daten werden dem Konstuktor als ByteArray übergeben.
Antworten werden eigentlich nicht beachtet. Gerade mal das ankommen
einer Nachricht wird signalisiert. Hier geht es für mich weiter mit der
Entwicklung.
Ich stehe allerdings vor dem Punkt, dass ich nicht so recht weiß wie ich
davon ausgehend weiter machen soll.
Vermutlich würden jetzt 90% von euch das alles über den Haufen werfen
und einen völlig anderen Ansatz verfolgen, aber das ist nicht mein Ziel.
Was ich nun allerdings überlege, ist ob es sinnvoll ist für jedes
Kommando ein Objekt von einem Basis-Paket abzuleiten, oder ob es
sinnvoller ist, diese Basiskommandos über Parameter zu "konfigurieren"?
Ich hoffe es ist verständlich was ich vor habe. Ist nicht immer leicht
sich kurz UND verständlich oder auch nur verständlich auszudrücken.
Vielen Dank für alle Antworten, Hinweise, Tips oder dergleichen!
Luigi
Luigi schrieb:> Die Daten werden dem Konstuktor als ByteArray übergeben.
Würde ich nicht machen. Beim Programmstart erzeugst du ein Object, nicht
jedesmal wenn Daten da sind.
In der Klasse machst du dann z.B. folgende Methoden:
bool haveNewData()
void switchLight(bool on)
void switchMotor(bool on)
bool isMotorOn()
void processNewDataFromSerial(char *data) // wird von der
Seriellen-Klasse aufgerufen
usw.
Also nur einmalig eine Instanz erzeugen, nicht jedes Mal eine Neue. In
der Klasse machst du dann alles nötige, Daten verarbeiten, senden
(lassen) usw.
Die Kommunikation über die serielle Schnittstelle würde ich auch wieder
über eine eigene Klasse machen.
Luigi schrieb:> Was ich nun allerdings überlege, ist ob es sinnvoll ist für jedes> Kommando ein Objekt von einem Basis-Paket abzuleiten, oder ob es> sinnvoller ist, diese Basiskommandos über Parameter zu "konfigurieren"?
Das erste klingt für mich grundsätzlich vernünftig. Etwas ähnliches habe
ich auch auf einem µC implementiert, aber den ganzen Klassenoverhead
weggelassen. Dort gibt es aber auch eine allgemeine
Paket-Parsing-Funktion. Die checkt dann erstmal, ob ein gültiges Paket
vorhanden ist, also bei mir ob die Checksumme passt, ob die Länge passt,
ob das Kommando existiert. Dann wird ein Funktionspointer aufgerufen je
nachdem welches Kommando gesendet worden ist. Übertragen auf den PC wo
Overhead egal ist würde ich dann in die abstrakte Basisklasse sowas wie
Checksum- und Längenprüfung machen.
Den zweiten Ansatz verstehe ich aber nicht wirklich. Das funktioniert
doch immer nur dann wenn die Kommandos ähnlich vom Aufbau sind. Aber
wenn zB zwischen Messdaten oder aber Debugstrings unterschieden werden
soll, skaliert das meiner Meinung nicht. Das endet dann in einer
riesigen if-else-whatever-Struktur die über kurz oder lang
unübersichtlich werden wird.
anfängt, macht man es falsch. Das kann der Compiler besser. Und wenn
das, was man machen will, zu kompliziert ist, um es in Polymorphismus zu
verpacken, dann ist es zu kompliziert und muss überarbeitet werden.
Wenn man aber die Message-Struktur um einen Callback und eine (im
Kommando übertragene) TelegrammID erweitert, sieht's schon freundlicher
aus. Dann kann man beim Empfang einer Antwort auch gezielter reagieren.
Luigi schrieb:> Ich stehe allerdings vor dem Punkt, dass ich nicht so recht weiß wie ich> davon ausgehend weiter machen soll.
Das liegt wahrscheinlich daran, daß du gar nicht weisst, was du denn nun
mit den Anwroten machen sollst.
Was soll passieren, wenn ein Paket nicht empfangen wurde ? "Weiss ich
nicht, macht der Aufrufer" führt zu "ich weiss auch nicht welche
Methoden dann passen".
Meistens will man eine Wiederholung, und wenn auch die nicht klappt eine
Erkennung daß "deine Platine" offline ist. Eventuell kann man sie dann
rücksetzen (senden eines breaks führt zum Rest?).
Es ist nicht verkehrt, erst mal ein low Level Objekt zu haben mit
Byte-Paketen die nur die Empfangen-Antwort analysiert,
und dann eine (oder mehrere) weitere Klassen, die diese Low Level
Objekte mit Datenstrukturen füllen und Rückantworten verarbeiten können,
z.B. abgefragte Eingänge entgegennehmen und speichern bzw. zur Anzeige
senden.
Luigi schrieb:> Nun habe ich mir ein "Objekt" geschrieben, welches quasi die serielle> Schnittstelle darstellt und Pakete/serille Kommandos verwaltet
Nun, genau das ist eigentlich der falsche Ansatz.
Objektorientiert programmiert man, wenn man eine Anzahl von innerlich
zwar unterschiedlichen, in der äußeren Behandlung jedoch gleiche Dinge
(eben Objekte) hat. Grafisches Zeug auf dem Bildschirm ist ein typisches
Beispiel: ob Liste odr Button oder Icon, alle müssen sich auf Kommando
selbst darstellen und auf Input reagieren - egal was sie innerlich sind.
Bei einem Handler für ne serielle Schnittstelle hingegen kann und sollte
man schlichtweg prozedural vorgehen - allerdings in separatem Thread.
Immerhin ist es ja so, daß da eben nix anderes ankommt als ein serieller
Bytestream, den es nach irgendwelchen Kriterien zu kanalisieren und zu
ordnen gilt. Die Schnittstelle selbst ist kein Objekt im eigentlichen
Sinne, denn es gibt ja nicht eine Vielzahl davon, die alle zugleich in
Aktion sind.
Erst oberhalb des o.g. Handlers wäre wieder an Objekte zu denken, also
wenn es zuvor einen vom Handler in die Queue geworfenen Event nebst
Datensatz gibt, der dann zum Kreieren eines Objektes führt. Aber das ist
dann schon unabhängig von allen seriellen oder sonstigen Schnittstellen.
W.S.
W.S. schrieb:> Bei einem Handler für ne serielle Schnittstelle hingegen kann und sollte> man schlichtweg prozedural vorgehen - allerdings in separatem Thread.> Immerhin ist es ja so, daß da eben nix anderes ankommt als ein serieller> Bytestream, den es nach irgendwelchen Kriterien zu kanalisieren und zu> ordnen gilt. Die Schnittstelle selbst ist kein Objekt im eigentlichen> Sinne, denn es gibt ja nicht eine Vielzahl davon, die alle zugleich in> Aktion sind.
nein
Eine Schnittstelle ist sehr gut als Objekt verwendbar. (Was übriges in
viele Sprachen auch so gemacht wird).
Warum sollte man viele Prozeduren schreiben, die alle das Handle
übergeben bekommen? Eventuell muss ja auch noch der Status verwaltet
werden (verbunden - nicht verbunden) Wo willst du den denn speichern?
Also zu jedem Handle auch noch ein haufen Variablen?
Machs erst mal prozedural soweit, bis alles funktioniert wie gewünscht.
Es handelt sich ja nur um eine Schnittstelle. Wenn du dann noch viel
Motivation hast, kannst Du ja das kompakte, funktionierende Programm in
eine OOP Scheisse umbauen und dann feststellen, daß Du den zwanzigfachen
Code / Overhead und keinerlei Vorteil davon hast. Also wozu ?
OK, vielen Dank Leute.
Ihr habt mir viel geholfen. Damit ist dann meine Entscheidung gefallen,
wie ich weiter mache (vererben).
Alles Andere ist in der tat nicht so recht zu ende gedacht und "mal
drauf los"-programmiert. Letzteres ist zwar mehr mein Stil, weil ich es
nie anders gelernt habe, aber zumindest abschnittsweise kann ich mir
Konzepte ausdenken ;)
Ob ich noch der große Programmierer werde, wage ich zu bezweifeln, aber
ich komme meistens ans Ziel. Und immerhin habe ich den Ansporn mich zu
verbessern.
Die Objektorientierung so verstehen, wie es manche glauben besser zu
wissen, wird für mich jedenfalls noch ein Meilenstein sein. Ich lerne
aber zunehmend schon jetzt die Vorzüge dieser (objektorientierten)
Hilfsmittel zu meinen Gunsten zu verwenden.
Vielen Dank nochmals
Grüße Luigi
Mal doch mal ein Klassenstrukturdiagram. Da kannst Du
Verantwortlichkeiten den Klassen zuordnen. Wenn Du das mal auf einem
Blatt siehst, dann erkennt man viele Probleme besser als in vielen
Source Dateien.
OOP Blähware Hasser schrieb:> Machs erst mal prozedural soweit, bis alles funktioniert wie gewünscht.
warum erst umständlich machen und dann vereinfachen?
> Es handelt sich ja nur um eine Schnittstelle.
Es ist auch nur ein Programm, auch da verwendet man mehrere Objekte.
> Wenn du dann noch viel> Motivation hast, kannst Du ja das kompakte, funktionierende Programm in> eine OOP Scheisse umbauen und dann feststellen, daß Du den zwanzigfachen> Code / Overhead und keinerlei Vorteil davon hast. Also wozu ?
Achso darum geht es, du willst es nur nicht.
Woher der Overhead kommen soll ist mir nicht klar. Ein Objekt was intern
nur ein Handle hat, hat auch nur die Größe von einen Handle und die
Methoden sind auch gleich groß.
Luigi schrieb:> Alles Andere ist in der tat nicht so recht zu ende gedacht und "mal> drauf los"-programmiert.
Erstmal (grob gezielt) drauf los und sehen, was herauskommt, ist bei
komplexen Problemen, die man noch nicht ganz versteht (also bei jedem
größerem Programm), vollkommen OK.
Man muss nur zwischendurch (nach jedem eingebauten Feature) regelmäßig
das bisher geschaffene analysieren, herausfinden, wie es besser ginge,
und dann auch gnadenlos umbauen. Basisklassen, Pattern etc. ergeben sich
dann teilweise von selbst. Und keine Scheu haben, alten Code (egal wie
viel Arbeit drinsteckt) neuzuschreiben (oder im besten Fall ganz zu
löschen), wenn das Gesamtsystem übersichtlicher wird. Wenn man um die
alten Fehler (die mal gute Ideen waren und sich erst später als
ungünstig erwiesen haben) herumprogrammiert, hat man dank
Schneeballeffekt irgendwann einen undurchschaubaren unwartbaren
Misthaufen.
W.S. schrieb:
> Bei einem Handler für ne serielle Schnittstelle hingegen kann und sollte> man schlichtweg prozedural vorgehen - allerdings in separatem Thread
Das ist mir IMHO eine falsche Verallgemeinerung. Warum soll ein eigener
Thread erzeugt werden...? Das hat mit der Frage nix zu tun.
Ja, ein Thread macht Sinn, wenn daa Senden und Empfangen nicht
blockieren sein darf, aber diese Info hat der TO nicht geliefert. Wenn
die Schnittstelle nach dem Prinzip Request/Response arbeitet, macht ggf
ein eigener Thread keinen Sinn.
Was mir immer wieder auffällt, egal ob prozedural oder OOP programmiert
wird: immer wird eine Lösung diskutiert oder ein Design erstellt, bevor
ueberhaupt die Aufgabe dargestellt ist und die möglichen Probleme
identifiziert...
Noch schlimmer ist es beim Entwurf eines Gerätes:
Die Hardwareker designern sofort Hardware, die Softwareker sofort
Software. Dabei ist das zuerst zweitrangig. Erst muss mal die Funktion
erfasst werden...