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.
Luigi schrieb: > oder ob es > sinnvoller ist, diese Basiskommandos über Parameter zu "konfigurieren"? Wenn man sowas baut
1 | enum eMsgType{ELEPHANT, CHEESE, BURN}; |
2 | |
3 | struct Message |
4 | {
|
5 | eMsgType type; |
6 | uint8_t data[16]; |
7 | };
|
und jede Funktion mit
1 | void do_things(const Message& msg) |
2 | {
|
3 | switch(msg.type) |
4 | {
|
5 | case ELEPHANT: toeroeh(*(ElephantContent*)msg.data); break; |
6 | case CHEESE: ........ |
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...
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.