Forum: PC-Programmierung C++ Klasse bleibt abstrakt, obwohl konkrete Implementierung vererbt wird


von Jasson J. (jasson)


Lesenswert?

Ahoi
-
jetzt kratze ich mich doch etwas am Kopf.

>Möglichst kurz zum Kontext
Ich versuche DMX orientiert Geräte zu abstrahieren. Dazu versuche ich 
mich erstmal am naheliegendsten, einem RGB Strahler.

Mein Ziel ist, dass das virtuelle Gerät direkt über seine Instanz 
mittels
.SetRed(value); etc ansprechbar ist, die Referenz aber auch in einer 
abstrakteren Variable wie RGB_Device gehalten werden kann.

Meine Idee war, zu jeder Farbe gibt es eine pur virtuelle Klasse á la
1
class I_DmxChannel<color>_8Bit
2
{
3
    virtual void Set<color>ChnlVal(uint8_t val, std::vector<uint8_t>& universum) = 0;
4
    virtual void Set<color>ChnlIndx(uint16_t index) = 0;
5
};

und für das genannte RGB_Device setze ich ein "zusammengerbtes" 
Interface zusammen nach
1
class I_Cluster_RGB : public I_DmxChannelRed_8Bit, public I_DmxChannelGreen_8Bit, public I_DmxChannelBlue_8Bit

Für jede abstrakte <color>-Klasse gibt es eine konkrete Implementierung
1
class <color>_8Bit : public I_DmxChannel<color>_8Bit
2
{
3
public:
4
    void Set<color>ChnlVal(uint8_t val, std::vector<uint8_t>& universum)
5
    {
6
        myChnl.SetChnlVal(val, universum);
7
    };
8
    void Set<color>ChnlIndx(uint16_t index)
9
    {
10
        myChnl.SetChnlIndx(index);
11
    };
12
13
private:
14
    DmxChannel_8Bit myChnl;

Das Interface für das RGB_Device klebe ich auch aus den einzelnen Farben 
zusammen. Ich denke, bei diesem konkreten Beispiel würde es noch ohne 
Interface gehen, aber vielleicht will man das selbe Hardware-Gerät als 
RGB+Weiß darstellen.
1
class I_Cluster_RGB : public I_DmxChannelRed_8Bit, public I_DmxChannelGreen_8Bit, public I_DmxChannelBlue_8Bit

Die konkrete Implementierung habe ich so versucht:
1
class RGB_Device : public Red_8Bit, public Green_8Bit, public Blue_8Bit, public I_RGB_Device
> Und diese kann nicht instaziert werden, weil sie als abstrakt gesehen wird. Kann 
mir das jemand erklären?

von Klaus H. (klummel69)


Lesenswert?

Ich bin verwirrt, kann aber auch am Rotwein um diese Uhrzeit liegen....

Kannst Du mal den vollständigen Sourcecode anhängen? Da fehlt einiges an 
Infos. (Da fehlt doch so etwas wie templates...?)

: Bearbeitet durch User
von Mark B. (markbrandis)


Lesenswert?

Jasson J. schrieb:
> Ich versuche DMX orientiert Geräte zu abstrahieren. Dazu versuche ich
> mich erstmal am naheliegendsten, einem RGB Strahler.

> Meine Idee war, zu jeder Farbe gibt es eine pur virtuelle Klasse á la

Zum Thema OOP generell:

Du willst ein Gerät in Software modellieren. Dieses Gerät ist ein 
RGB-Strahler.

Das Gerät hat nun zum Beispiel eine rote, eine blaue und eine grüne LED 
verbaut. Das ist eine hat ein Beziehung.

Wenn man objektorientiert vernünftig modellieren will, sollte man die 
"ist ein" und die "hat ein" Beziehungen vernünftig auseinander halten. 
Das sind zwei verschiedene Dinge.

> Die konkrete Implementierung habe ich so versucht:

> class RGB_Device : public Red_8Bit, public Green_8Bit, public Blue_8Bit, public 
I_RGB_Device

Ein Gerät von einzelnen Komponenten abzuleiten ergibt nicht viel Sinn. 
Stattdessen kann Dein Gerät die Komponenten als Member-Variablen 
beinhalten, zum Beispiel so:
1
#include "LED.h" // for class LED
2
3
class RGB_Device
4
{ 
5
    private:  
6
    LED Red_8Bit; 
7
    LED Green_8Bit;
8
    LED Blue_8Bit;
9
    
10
    public: 
11
    // public member or methods here
12
    
13
    // etc.

von Jasson J. (jasson)


Angehängte Dateien:

Lesenswert?

> Zum Thema OOP generell:
>
> Du willst ein Gerät in Software modellieren. Dieses Gerät *ist ein*
> RGB-Strahler.
>...

Das war (oder ist) letztlich auch mein Ziel. Ich hatte die Idee, 
feingranulare - also im Prinzip einkanalige - Geräte zu haben, aus denen 
ich wie mit
KLEMMBAUSTEINEN (nicht Lego :>) größere Geräte zusammensetzten kann und 
dabei schematisch gleichen Code vermeiden möchte. Ich häng mal ein Bild 
an.
-
Mit einer Hat-ein Beziehung müssten die Methoden die zu einer virtuellen 
Repräsentation passen noch mal widerholt werden. Zwar den Code nicht, 
aber die Bodys müssten noch mal getippt werden und darin auf die 
passenden Member zugegriffen werden.

von Jasson J. (jasson)


Lesenswert?

Klaus H. schrieb:
> (Da fehlt doch so etwas wie templates...?)

Ah ja, meine Schreibweise mit < > war nicht clever. Damit meine ich 
nicht Templates sondern, Platzhalter für die Kanalfarbe oder Funktion.

Davon
1
class I_DmxChannel<color>_8Bit
2
{
3
    virtual void Set<color>ChnlVal(uint8_t val, std::vector<uint8_t>& universum) = 0;
4
    virtual void Set<color>ChnlIndx(uint16_t index) = 0;
5
};
gibt es als Code z.B.
1
class I_DmxChannelRed_8Bit
2
{
3
    virtual void SetRedChnlVal(uint8_t val, std::vector<uint8_t>& universum) = 0;
4
    virtual void SetRedChnlIndx(uint16_t index) = 0;
5
};

von J. S. (jojos)


Lesenswert?

Warum sollten verschiedene Farben eigene Klassen sein? Das macht gar 
keinen Sinn. Auch ein Template nicht weil die Farben nicht 
unterschiedliche Typen sind.
Also so wie Mark es schon vorgeschlagen hat.

von Jasson J. (jasson)


Lesenswert?

Ich bräuchte neben der Aussage, 'dass' es keinen Sinn macht eine 
Begründung, um zu verstehen, 'warum' es keinen Sinn macht.

Umgekehrt habe ich "ja" eine Begründung, warum ich es - zumindest 
aktuell - sinnvoll finde:

Ein physisches Gerät Typ_A hat Kanäle
- rot
- grün
- blau
- Pan

Ein physisches Gerät Typ_B hat Kanäle
- rot
- grün
- blau
- weiß
- amber
- UV
- Dimmer
- Pan
- Tilt

dann macht es in meinen Augen Sinn, die einzelnen Kanäle quasi wie 
einzelne Klötzchen über Ist-ein zu einem gesamten zusammensetzen zu 
können und damit direkt die jeweiligen Methoden zu haben. Mit einer 
Hat-ein Beziehung müsste ich bei jedem Gerätetypen die Methoden der 
Kanäle erneut abtippen um sie nach außen nutzbar zu machen, obwohl der 
Code immer der gleiche ist.

Um das Beispiel mal zu übernehmen
1
#include "LED.h" // for class LED
2
class RGB_Device
3
{ 
4
    private:  
5
    LED Red_8Bit; 
6
    LED Green_8Bit;
7
    LED Blue_8Bit;
8
    
9
    public:
10
    void SetValueRed(uint8_t val) {Red_8Bit.SetValueRed(val);}
11
    void SetValueGreen(uint8_t val) {Green_8Bit.SetValueGreen(val);}
12
    void SetValueBlue(uint8_t val) {Blue_8Bit.SetValueBlue(val);}

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?

Der Unterschied zwischen den drei Kanälen R, G und B ist irrelevant. Die 
drei verhalten sich exakt identisch - nur die LED, die hinten dranhängt, 
macht was anderes.

Es hat hier auch keinen Sinn, eine LED-Basisklasse zu verwenden und die 
Farb-LED-Klassen davon abzuleiten, denn was machen die 
unterschiedliches? Nichts. Der einzige Unterschied liegt in der 
Hardware.

von J. S. (jojos)


Lesenswert?

Ein 3 Kanal Gerät ist ja auch etwas anderes als ein 6 Kanal Gerät.
Das setValue könnte auch ein Argument enum Color bekommen.

von Rolf M. (rmagnus)


Lesenswert?

Jasson J. schrieb:
> Ich bräuchte neben der Aussage, 'dass' es keinen Sinn macht eine
> Begründung, um zu verstehen, 'warum' es keinen Sinn macht.
>
> Umgekehrt habe ich "ja" eine Begründung, warum ich es - zumindest
> aktuell - sinnvoll finde:
>
> Ein physisches Gerät Typ_A hat Kanäle

Richtig, es hat. Bei deiner Umsetzung ist ein Gerät aber 
unterschiedliche Kanäle. Das ist genau das, was hier gemeint war:

Mark B. schrieb:
> Wenn man objektorientiert vernünftig modellieren will, sollte man die
> "ist ein" und die "hat ein" Beziehungen vernünftig auseinander halten.
> Das sind zwei verschiedene Dinge.

> - rot
> - grün
> - blau
> - Pan

Aber warum müssen diese Kanäle von unterschiedlichen Klassen sein? 
Unterschiedliche Klassen macht man, weil sie sich unterschiedlich 
verhalten. Das Verhalten der Klasse hängt aber nicht von der Farbe ab. 
Diese ist eher ein Attribut.

> Ein physisches Gerät Typ_B hat Kanäle
> - rot
> - grün
> - blau
> - weiß
> - amber
> - UV
> - Dimmer
> - Pan
> - Tilt
>
> dann macht es in meinen Augen Sinn, die einzelnen Kanäle quasi wie
> einzelne Klötzchen über Ist-ein zu einem gesamten zusammensetzen zu
> können und damit direkt die jeweiligen Methoden zu haben.

Ich würde da eher eine einzige Klasse für alle Farben machen und dann 
dem Strahler z.B. ein Array/vector oder eine Map aus Kanälen mitgeben. 
Du musst dir dann nur eine gute Möglichkeit überlegen, wie man eine 
Farbkomponente gezielt auswählt.

> Mit einer Hat-ein Beziehung müsste ich bei jedem Gerätetypen die Methoden
> der Kanäle erneut abtippen um sie nach außen nutzbar zu machen, obwohl
> der Code immer der gleiche ist.

Du brauchst eigentlich nur eine Funktion, der du halt noch sagst, 
welchen Kanal du jetzt ansprechen möchtest. Getrennte Funktionen für 
jede Farbe halte ich nicht für sinnvoll.
Alternativ könnte dein Strahler eine Funktion haben, mit der du dir eine 
Referenz auf den gewünschten Kanal geben lassen kannst. Und den kannst 
du dann einfach über seine eigenen Memberfunktionen verändern.

von Jasson J. (jasson)


Lesenswert?

Rolf M. schrieb:
> Aber warum müssen diese Kanäle von unterschiedlichen Klassen sein?
> Unterschiedliche Klassen macht man, weil sie sich unterschiedlich
> verhalten. Das Verhalten der Klasse hängt aber nicht von der Farbe ab.
> Diese ist eher ein Attribut.

Das könnte der springende Denkanstoß sein
-
Das stimmt, meine Kanalklassen verhalten sich identisch. Die Idee einem 
identischen Verhalten verschiedene Typen zu geben war, darüber die 
Implementierung von Interfaces mit zu liefern, damit sich z.B. jedes 
Gerät, dass von Rot-Grün-Blau geerbt hat, bei einem Consumer als 
I_RGB_Device anmelden kann.
-
ist oft schwierig die Balance zu finden, zwischen das konkrete Problem 
das man hat zu beschreiben und den ganzen Rest drum rum, warum man es 
"so" lösen will. Das blurt die eigentliche Frage oft aus.

Aber wie gesagt - über den Denkanstoß denke ich nach

: Bearbeitet durch User
von Klaus H. (klummel69)


Lesenswert?

Mir hilft es oft, den nächst höheren Level zu betrachten:

* Wer arbeitet mit dieser Klasse/Objekt?
* Wie einfach/komplex wird die Nutzung?

Dann merkt man schnell, dass das Interface vielleicht unten super 
einfach zu erstellen war, aber darüber die Probleme erst beginnen.
Wie würdest du die konkreten Lampen/Instanzen (z.B. aus einer Liste mit 
3x RGB, 2*RGBW, 4x RGBWA) dann nutzen? Wie erkennt die Applikation, dass 
Device2 aus der großen Liste 2 Attribute mehr hat als Device1?

von Jasson J. (jasson)


Lesenswert?

Der Nutzer / Consumer der Lampen wäre etwas, dass ich inzwischen 
"Korrespondenz" taufen würde - etwas das einen Eingangswert auf ein oder 
mehrere Ausgangswerte Abbildet.
Beitrag "Re: Welches mathematisches Konzept bildet 1 Eingangswert auf 1+n Ausgangswerte ab?"
Könnte z.B. das klassische Farbrad sein, dass 0-255 auf Werte für 
Rot-Grün-Blau abbildet
-
und diese dann über ein Interface I_RgbDevice in alle Geräte spült, die 
sich unter dem Interface angemeldet haben.

Dann könnte ein RGB-W Gerät z.B. verschiedene Interfaces unterstützen
I_RGB
I_WhiteSpectrum
I_WhiteOnly

von Falk S. (falk_s831)


Lesenswert?

Grundsätzlich stellt sich, wie oben von den anderen bereits erwähnt, 
wofür du die Abstraktion brauchst. So wie ich das verstehe, möchtest du 
quasi ein [https://refactoring.guru/design-patterns/strategy](Strategy 
Pattern) implementieren für alle denkbaren Kombinationen von Sensoren.

Das hat mit Vererbung auch tendenziell eher wenig zu tun (btw: deine 
Interface-Klassen benutzen finale Destruktoren in der Basisklasse -> 
possible Memleak), sondern ist die vtable ja bloß ein Mittel zur 
aktuellen Methode: du rufst ja nicht wirklich Basisklassencode auf.

Die Idee ist finde ich erstmal nicht verkehrt, allerdings kommst du mit 
einem zu generischen Ansatz schnell in Teufels Küche. Such dir lieber 
3-4 konkrete Aktoren heraus, implementier diese erstmal im Code aus und 
such dann erst wenns schon funktioniert nach einer Abstraktion.

von Daniel A. (daniel-a)


Lesenswert?

Rolf M. schrieb:
> Richtig, es hat. Bei deiner Umsetzung ist ein Gerät aber
> unterschiedliche Kanäle.

Das würde ich jetzt nicht so unterschreiben. Er implementiert 
Interfaces. Die Klassen gibt es nur, weil C++ halt keine echten 
Interfaces hat.

Wenn ich bei einer Klasse Dog und einer Klasse Cat ein interface 
CanMakeNoise Implementiere, dann ist das aus Design technischer Sicht 
keine ist ein und keine hat ein Beziehung. Man markiert lediglich alle 
Objekte die bestimmte Eigenschaften / Aktionen haben. Ich weiss gerade 
nicht, wie man das Pattern nennt, aber z.B. in Java ist es nicht 
unüblich.

Sinnvoll ist es hier vermutlich aber trotzdem nicht.

von Torben H. (bauteiltoeter)


Lesenswert?

Hi,

als jemand der sowohl Software schreiben kann als auch mit Lichtpulten 
umgehen kann:
Dein Ansatz hat einige fundamentale Probleme.

1) Du willst eigentlich deine Lampenbeschreibung nicht hart 
reinprogrammieren sondern dynamisch aus Configdateien / Datenbanken 
laden. Sonst musst du ja für jeden neuen Scheinwerfertyp die Software 
ändern?
Das mag je nach Anwendungsfall noch ok sein.

2) Du musst dir ein Parameter-System ausdenken das flexibler ist. deine 
Klasse sollte nicht .setRed() .setGreen() und .setBlue() haben. Was 
machst du, wenn du plötzlich ein Gerät mit mehreren Zellen hast und jede 
Zelle R/G/B/W/A/UV-LEDs hat?
Um flexibel zu bleiben brauchst du eine Geräte-Klasse, die für sich eine 
Datenstruktur aus Kanälen beinhaltet. Ein Kanal wäre dann z.B. ein 
8-Bit-Dimmer, ein 16-Bit-Dimmer, ein Rotwert, ein Grünwert u.s.w.
Damit kannst du dann dann auch kompliziertere Gerät abbilden.
Und anstatt Setter für R-G-B machst du lieber eine Funktion, die einen 
RGB-Wert nimmt und selber in verschiedene Farbsysteme umrechnet. Es gibt 
z.B: auch oft Lampen mit CMY-Mischer statt RGB (alles, was eine 
Weißlichtquelle hat und mehr Optionen bietet als ein einfaches Farbrad).

von Sven B. (scummos)


Lesenswert?

Hi,

nicht böse gemeint aber dein mentales Modell der involvierten 
Programmierkonzepte ist zu schlecht um aus dem Stand eine Abstraktion 
für sowas hinzuschreiben. Was du da vorschlägst ergibt wenig Sinn.

Ich empfehle stattdessen: schreib' erstmal konkret, auf die einfachste 
mögliche Weise, also ohne irgendeine vermeidbar Abstraktion, ein paar 
deiner Geräte so auf, dass es funktioniert. Benutze dazu erstmal ganz 
einfache Konzepte wie freie Funktionen (aber sparsam! nicht tausend 
Stück) mit Argumenten und Rückgabewerten, enums, und switch/case und 
if/else zur Unterscheidung der Geräte, wenn nötig. Keine Klassen, keine 
Vererbung, keine Templates, keine virtuals.

Dann, wenn dieser einfache Code mal 500+ Zeilen hat, schaust du mal 
drauf und suchst nach dem Muster, was man am einfachsten da 
rausabstrahieren könnte. Dafür überlegst du dir dann unter Anwendung der 
bisher vermiedenen Konzepte eine geeignete Lösung.

Schreib den Code auf die dümmste mögliche Art stur untereinander und 
abstrahiere nur, wenn du dir wirklich sicher bist dass es diesen 
dümmsten möglichen Code deutlich vereinfacht.

Nicht umgekehrt alle Konzepte die du irgendwo mal gehört hast 
draufwerfen in der Hoffnung dass es besser wird. Wird's nicht. Viel 
besser zu wenig abstrahiert als falsch.

Wenn man viel Erfahrung in Softwareentwicklung und Erfahrung in der 
Domain (Beleuchtung) hat, kann man auch "aus dem Stand" direkt 
Abstraktionen erraten, die vermutlich gut sind. Du kannst das nicht. Das 
macht aber nichts -- außer du versucht es trotzdem ;)

Viel Erfolg!

: Bearbeitet durch User
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.