Forum: PC-Programmierung Sauber objektorientiert Programmieren; Brauche kleine Hilfestellung


von Luigi (Gast)


Lesenswert?

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

von Fritz G. (fritzg)


Lesenswert?

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.

von Horst (Gast)


Lesenswert?

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.

von Tom (Gast)


Lesenswert?

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.

von Horst S. (Gast)


Lesenswert?

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.

von Michael B. (laberkopp)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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?

von OOP Blähware Hasser (Gast)


Lesenswert?

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 ?

von Luigi (Gast)


Lesenswert?

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

von Andreas R. (daybyter)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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

von Tom (Gast)


Lesenswert?

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.

von Marc (Gast)


Lesenswert?

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

von Stefan S. (energizer)


Lesenswert?


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.