C++ Klasse bleibt abstrakt, obwohl konkrete Implementierung vererbt wird

#7676546
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?

#7676573
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.
#7676584
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.

Angehängte Dateien:
#7676585
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
};
#7676649
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);}
#7676655
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.

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

#7676691
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

#7676730
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?

#7676759
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

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

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

#7678419
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).

#7678730
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!

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren