Designfrage: Interpretation Byte-Sequenz
-
Hallo in die Runde,
ich benötige Meinungen zu einem Konzept. Folgende Ausgangssituation: Ich bekomme eine Byte-Folge von 8 Byte, wobei das erste Byte entscheidet, wie die restlichen 7 Byte zu interpretieren sind. Diese 7 Byte sind binäre Abbilder von 3 unterschiedlichen Klassen (keine Gemeinsamkeiten, weder Member noch Methoden). Da ich bei Einlesen der Bytefolge IMHO nicht diese Klassen anhand des ersten Bytes erzeugen kann, habe ich mir überlegt, einfach den Selektor und die restliche Sequenz so als Klassen-Member anzulegen, und die verschiedenen Interpretationen per Aufruf zu generieren.
Stark verkürztes Bsp:
class ABC { public: //Ctors, Dtors... enum ABCEnum {A, B, C}; ABCEnum getSelector() const; A getSequenceInterpretationA() const; B getSequenceInterpretationB() const; C getSequenceInterpretationC() const; void setSequence(const A&); //includes changing selector void setSequence(const B&); void setSequence(const C&); private: ABCEnum selector; unsigned char sequence[7]; }Das heisst auch, dass jeder Benutzer der Klasse ABC vorher den Selector fragen sollte, um die richtige Interpretationsfunktion zu rufen.
Meine Frage ist nun: Eigentlich kennt ABC an sich ja die richtige Interpretationsfunktion. Kann also diese Problem irgendwie eleganter gelöst werden?
Vielen Dank!
-
Hab mich mal registriert, ist ja ein lehrreiches Forum :-).
Eine Erweiterung für die Interpretationsfunktionen ist mir noch eingefallen: Man könnte eine Exception auslösen, wenn eine nicht selektor-richtige Interpretationsfunktion gerufen wurde...
-
boost Variant könnte eine elegante Lösung darstellen.
class ABC { // ... boost::variant<A,B,C> getSequence() const; };Je nach Kontext könnte es vorzuziehen sein, das Variant-Objekt oder sonst die Byte-Sequenz by Bedarf zu generieren.
-
Ich bekomme eine Byte-Folge von 8 Byte, wobei das erste Byte entscheidet, wie die restlichen 7 Byte zu interpretieren sind.
Daraus schliesse ich: es muss eine Gemeinsamkeit geben. Wie ich darauf komme? Ganz einfach, wenn an einer bestimmten Position im Input entweder A, B oder C stehen kann, dann haben A, B und C schonmal die Gemeinsamkeit dass es eben Stellen gibt wo eines der Drei im Input stehen kann. Logisch irgendwie

Und da eben A, B oder C an ein und der selben Stelle im Input auftauchen kann vermute ich weiter dass es noch weitere Gemeinsamkeiten geben wird.
Diese 7 Byte sind binäre Abbilder von 3 unterschiedlichen Klassen (keine Gemeinsamkeiten, weder Member noch Methoden).
Siehe oben.
Vielleicht hast du ja auf ein paar (gemeinsame) Funktionen "vergessen", oder siehst diese nicht als Teil der Klassen A, B und C sondern als Client Code.Man könnte aber besser etwas zu dem Thema sagen wenn man wüsste um was für Klassen und um was für ein Programm es sich dabei handelt. Oder ist das geheim?
-
Es ist ungünstig, alle Klassen A, B und C wieder in einer gemeinsamen Klasse zusammenzufassen. Besser ist es, die Klassen einzeln zu betrachten. Wie hustbaer schon erwähnt hat, muss es eine Gemeinsamkeit geben - es werden alle Objekte irgendwie eingelesen. Folglich ist es sinnvoll, sich eine Basisklasse zu schaffen, die ein Interface für das Einlesen bereitstellt. Etwa so
#include <iostream> #include <sstream typedef std::istream IN; class Base { public: virtual ~Base()=0 {} friend IN& operator>>( IN& in, Base& obj ) { obj.read( in ); return in; } protected: virtual void read( IN& in )=0; };Da ich nicht weiß, von wo eingelesen werden soll, habe ich diese Quelle mal mit dem Typ 'IN' versehen und unterstellt, dass sie ein Stream-ähnliches Interface besitzt.
Nun die abgeleiteten Klassen, die selbstverständliche jede ihre individuelle read-Methode implementieren müssen.
class A : public Base { protected: virtual void read( IN& in ) { // ... Info für A lesen } }; class B : public Base { protected: virtual void read( IN& in ) { // ... Info für B lesen } };Und eine Factory-Funktion, die an einem Pointer auf die Basisklasse ein konkretes Objekt dranhängt.
// -- Factory-Function erzeugt ein A, B oder C -Objekt IN& operator>>( IN& in, Base*& pObj ) { typedef unsigned char Byte; Byte b1; if( in >> b1 ) { switch( b1 ) { case 'a': pObj = new A; break; case 'b': pObj = new B; break; // case 'c': usw. default: in.setstate( std::ios_base::failbit ); // Fehler, unbekanntes Byte1 // oder throw exception return in; } in >> *pObj; // Objekt lesen } return in; }Falls es (viel) mehr als drei Klassen werden sollten, so könnte man hier auch eine Registry mit je einer Factory-Funktion für jede Klasse imlementieren. Das hat dann den Vorteil, weitere Klassen hinzufügen zu können, ohne an dieser Funktion noch etwas ändern zu müssen.
In einer Anwendung sieht das dann skizzenhaft etwa so aus:
int main() { using namespace std; istringstream buf("a2345678"); Base* obj = 0; if( buf >> obj ) { // 'obj' zeigt auf ein A-Objekt delete obj; // .. besser Smart Pointer nutzen } return 0; }Jetzt stellt sich zwangsläufig die Frage, was mit dem Objekt geschehen soll!
Sind A, B und C zufällig Meldungen von einem Feldbus oder ähnlichem?
Gruß
Werner
-
Danke erstmal für eure schnellen Antworten. Ich geh mal auf die einzelnen Posts ein:
@camper: interessanter Ansatz, jedoch möchte ich das Benutzen zusätzlicher Bibliotheken vermeiden.
@hustbaer und Werner Salomon:
Gut, ich denke, es wäre sinnvoll, mehr über die Klassen A, B, C zu sagen. Sie stellen Perioden ähnlich wie bei Kalenderfunktionen dar.
Die 7 Byte sollen je nach Selektor einmal- als Zahl interpretiert werden (gekapselt durch A, mit Casting-Operator nach long long). Die Zahl stellt eine fixe Differenz zwischen Zeitpunkten, an denen etwas passieren soll, dar.
- als 3 aufeinanderfolgende Bitfelder: Monate, Tage, Wochentage (gekapselt durch B). Dort wo die Bits gesetzt werden, passiert etwas zu genau diesem Zeitpunkt.
- als 7 Byte Jahrestage-Schlüssel (Bsp. zu berechnende Feiertage)
Ein Anwender soll die Möglichkeiten haben, (hübsch übersetzt durch eine GUI) zu sehen, welche Periode eingestellt, soll diese ändern können oder einen anderen Periodentyp wählen können. Das Ganze wird dann wieder rausgeschrieben und in einem eingebetteten System verarbeitet.
Ich habe zuerst auch an eine Factory-Möglichkeit gedacht, diese aber verworfen, da ich die Typen genau kennen muss und nicht nur die gemeinsame Schnittstelle. Mit "dynamic_cast" wollt ich eigentlich nicht arbeiten, denn dann wäre dieses Konzept nicht wirklich sinnvoll, oder?
Danke aber für eure bisherigen Vorschläge!