Hallo,
ich möchte folgendes erreichen:
Ein Programm, eine Art Textinterpreter von Skriptbefehlen, lädt eine
XML-Datei in der dem Interpreter bekannte Objektarten definiert sind.
Die Objekte werden so abgebildet:
<objects>
<Obj type="Obj1">
<param type="uint8_t" name="p1" />
<param type="uint8_t" name="p2" />
</Obj>
<Obj type="Obj2">
<param type="uint8_t" name="p1" />
<param type="char*" name="p2" />
</Obj>
<Obj type="Obj3">
<param type="uint8_t" name="p1" />
<param type="uint8_t" name="p1" />
<param type="Obj1" name="p3" />
</Obj>
</objects>
Dahinter verbirgt sich eine Liste von Objekten (Obj) die über 0 bis n
Parameter verfügen. Die Parameter sind die zu übergebenden
Konstruktorparameter des Objekts und können sowohl Basisdatentypen wie
int, char usw. als auch Zeiger auf andere Objekte sein.
Jedes Objekt ist eine dem Interpreter bekannte C-Klasse, die ein Attibut
besitzt, das identisch mit dem "type"-Attribut von Obj ist. So kann der
Interpreter eine Verbindung zwischen C-Klasse und XML-Objekt herstellen.
Nun sollen durch das Interpreterskript zur Laufzeit Objekte erzeugt
werden können, die in der XML-Datei beschrieben worden sind. Also z.B.
"Create Obj2("NameDerInstanz",p1,p2);"
Im Hintergrund verwertet der Interpreter dieses Skript vereinfacht
beschrieben zu einem C++-typischen
1
obj2*MyObject=newobj2(p1,p2,"NameDerInstanz");
Der zu "Obj2" passende Klassenkonstruktor muß jetzt irgendwie (und
genau das ist meine Frage) gefunden werden können, da der Konstruktor
von Obj1 oder Obj3 nicht passt. Anschließend kann der Interpreter dieses
Objekt dann eindeutig zuordnen und z.B. folgende Skriptoperationen
ausführen:
obj("NameDerInstanz").Read(&data,25);
die intern so verarbeitet werden:
Sonstiges:
Alle Objekte haben eine gemeinsame abstrakte Basisklasse, die
POSIX-Methoden wie READ, WRITE usw. implementiert. Wie kann ich nun
dieses
1
obj2*MyObject=newobj2(p1,p2,"NameDerInstanz");
während der Programmausführung erzeugen? Alles über switch-case-Ketten
zu verknüpfen geht zwar, aber ist nicht sonderlich schön. Gibt es auch
eine elegantere Möglichkeit?
Ich glaube nicht das das mit C/C++ geht, ausser mit dem einem grossen
case construct.
Für soetwas ist .net oder java besser geeignet. Dort gibt Object aus
"strings" generieren.
object = new GetClassOf("klassen name");
(ist nur ein beispiel, die methoden heisen dafür anders)
Factory Pattern
Dynamisch, also ohne Interpreter geht das nicht, da C++ keine Laufzeit
Informationen mehr hat. Das heisst Du musst:
if (name == "KlasseXXX") obj = new KlasseXXX(...);
Eine dynamische Namensgebung der Instanz ist nicht möglich (insofern,
dass sie im C++ Code dann nicht unter diesem Namen ansprechbar ist). Du
müsstest einen eigenen Interpreter schreiben, quasi eine Scriptsprache à
la Python.
(also unter dem Vorbehalt, Deine Frage richtig verstanden zu haben :-).
Ich denke, du hast hier
> Jedes Objekt ist eine dem Interpreter bekannte C-Klasse,> die ein Attibut besitzt, das identisch mit dem "type"-Attribut> von Obj ist.
schon deinen Designfehler.
Was du bräuchtest, ist eine Klasse, die in der Lage ist alle die
Objektkategorien, die du durch
<Obj type="Obj1">
<param type="uint8_t" name="p1" />
<param type="uint8_t" name="p2" />
</Obj>
bzw. deren Variationen beschreiben kannst, darzustellen.
Ansonsten:
Du suchst nach einem Factory-Pattern.
Jede Objektklasse registriert sich beim Hochfahren des Pgms. mit seinem
Typ (und zb. einem template Objekt) bei der Factory. Wird ein Objekt
eines bestimmten Typs benötigt, so durchforstet die Factory ihre Liste
von bekannten Typen und erzeugt das benötigte Objekt (zb indem es das
template Objekt auffordert eine Kopie von sich selbst zu erzeugen.
Stichwort virtueller Copy Constructor, also eine virtuelle clone()
Funktion).
Anstelle des template Objektes könnte man auch jeder Klasse eine
statische create Funktion verpassen und die Factory speichert sich einen
Pointer auf die zu jedem Typ gehörende create Funktion (ist vielleicht
einfacher als über einen vCCtor zu gehen)
Aber ich denke ernsthaft, dass der ganze Ansatz mit "Das Programm hat
für jede im XML beschriebene Objekt-Kategorie eine Klasse mit" schon
falsch ist. Das folgt schon alleine daraus, dass einigen Aufwand
bedeuten würde sicherzustellen, dass sich XML-Beschreibung und in C++
codierte Klassen nicht widersprechen.
Entweder das C++ Programm ist in der Lage, aus der XML Beschreibung
intern eine Objektstruktur zu entwickeln (die alle mit nur einer Klasse
dargestellt werden), oder aber das C++ Programm hat tatsächlich einige
Klassen für unterschiedliche Objekttypen parat und dafür fallen die
entsprechenden Beschreibungen aus dem XML raus. Im XML gibt es dann
'magisch' ein paar Objekttypen, die benutzt werden können.
Beides gemeinsam wird auf lange Sicht für Ärger sorgen (nämlich dann,
wenn sich XML und C++ über den Objektaufbau widersprechen, was zb leicht
bei zukünftigen Erweiterungen passieren kann)
Vielen Dank für die schnellen Antworten. Die Aussage mit dem
Designfehler habe ich nicht ganz verstanden? Bis auf die Tatsache, dass
man XML und C-Klassen "konsistent" halten muß, ist es doch nicht
schlecht?
Es ist noch dazuzusagen, dass ich die abgeleiteten C-Klassen nicht
großartig ändern sollte, es sind über 450 Stück, die von etwa 10
verschiedenen Entwicklern über Jahre hinweg geschrieben wurden. Diese
sind als "gegeben" anzusehen. Eine Änderung an der gemeinsamen
Basisklasse, oder das hinzufügen einer überladenen Template-Funktion
wäre besser. Hintergrund ist es, eine Art Schnittstellentester
(Posix-Funktionen) für die einzelnen Klassen zu erzeugen.
Die Parameter der Klassen können unterschiedlich oder identisch sein und
dennoch ist die dahinterstehende Funktionalität völlig anders. Eine Art
Klassenbeschreibungs-String ist doch deshalb zwingend nötig, oder?
Gibt es noch einen anderen Ansatz, wie man in einen Interpreter elegant
durch "Texteingabe" im weitesten Sinne, Objekte von beliebigen aber
ihm bekannten C-Klassen erzeugen könnte? Vielleicht sogar ganz ohne XML?
Alex Wurst schrieb:
> Vielen Dank für die schnellen Antworten. Die Aussage mit dem> Designfehler habe ich nicht ganz verstanden? Bis auf die Tatsache, dass> man XML und C-Klassen "konsistent" halten muß, ist es doch nicht> schlecht?
Dann warte ab, bis deinen Benutzern es das erste mal passiert, dass die
beiden Beschreibungen nicht mehr zusammenstimmen.
Viel Spass beim Fehlersuchen :-)
> sind als "gegeben" anzusehen. Eine Änderung an der gemeinsamen> Basisklasse, oder das hinzufügen einer überladenen Template-Funktion> wäre besser.
Wenn deine Klassen bereits einen vCCtor haben, hast du gewonnen. Daher
die Frage (du musst nur in der Basisklasse nachsehen):
Gibt es eine Funktion
1
classBasis
2
{
3
public:
4
...
5
6
virtualBasis*clone();
7
};
(manche Entwickler nennen die Funktion auch copy())
Funktionalität: Ein Objekt soll eine Kopie von sich selbst erzeugen.
Eine typische Implementierung sieht so aus
pCopy ist dann ein Pointer auf eine Kopie von *pIrgendwas. War
pIrgendwas ein Derived1 Objekt, dann zeigt auch pCopy auf ein Derived1
Objekt. War *pIrgendwas ein Derived2 Objekt, dann zeigt auch pCopy auf
ein Derived2 Objekt.
Gibt es keinen derartigen Mechanismus und darfst du die Klassen nicht
ändern: By,by OOP; welcome switch-case
Alternativ könnte es auch noch geben (ist aber seltener, wenn man das
nicht von vorne herein hineindesigned)
1
classBasis
2
{
3
public:
4
...
5
6
staticBasis*create();
7
};
8
9
classDerived1:publicBasis
10
{
11
public:
12
13
staticBasis*create(){returnnewDerived1;}
14
};
15
16
classDerived2:publicBasis
17
{
18
public:
19
20
staticBasis*create(){returnnewDerived2;}
21
};
> ihm bekannten C-Klassen erzeugen könnte? Vielleicht sogar ganz ohne XML?
XML ist nur das Transportmedium. Ob du XML benutzt oder eine ASCII Datei
oder sonstirgendetwas anderes, löst das Problem nicht.
Einen virtuellen CopyConstructor gibt es leider nicht.
Eine neue Methode in den abgeleiteten Klassen hinzuzufügen wäre
prinzipiell schon möglich - sofern einfach realisierbar - aber halt mit
einigem Anpassungsaufwand verbunden. Die Grundfunktion der Klassen muß
halt unverändert weiterbestehen.
Die Basisklasse beschreibt folgende Methoden:
1
IoStatusOpen(IoMode);
2
IoStatusClose(void);
3
virtualvoidInit(void)=0;
4
virtualvoidStop(void)=0;
5
virtualIoStatusRead(IoDataRef,IoDataSize);
6
virtualIoStatusWrite(IoDataRef,IoDataSize);
7
virtualIoStatusIoctl(IoCmd,IoCmdData);
8
virtualIoStatusSeek(IoSeekOffset,IoSeekOrg);
9
virtualIoStatusFlush(void);
10
virtualIoStatusSync(void);
Angenommen, eine Änderung in den Klassen wäre kein Problem, wie geht's
dann weiter? Jede erhält einen CC? Und wie "meldet sich eine Klasse an"
z.B. in einer verketteten Liste, von der es noch gar kein Objekt gibt?
Denkfehler meinerseits? Und letzte Frage: Wie handhabe ich es, wenn jede
Klasse andere Parameter hat die wiederum der Interpreter nicht kennen
kann?
Alex Wurst schrieb:
> Angenommen, eine Änderung in den Klassen wäre kein Problem, wie geht's> dann weiter? Jede erhält einen CC? Und wie "meldet sich eine Klasse an"> z.B. in einer verketteten Liste, von der es noch gar kein Objekt gibt?> Denkfehler meinerseits?
Mal angenommen du gehst den Weg über vCCtor (create Funktion würde
ähnlich funktionieren)
Dann brauchst du einen Mechanismus, der den Typ (bei dir ein String) mit
einem Objekt dieses Typs verknüpft.
Da bauen wir doch gleich mal eine Klasse dafür (man könnte auch ein
std::pair dafür verwenden)
eine Factory hält dann zb eine Liste derartiger TypTemplates und eine
Funktion, die einen Typstring nimmt, ihn sucht und wenn es einen Eintrag
dafür findet, das typeTemplate auffordert eine Kopie von sich zu
erzeugen, welches sie dann zurückgibt
1
classTypeFactory
2
{
3
public:
4
Basis*create(conststd::string&type);
5
6
protected:
7
std::vector<TypeTemplate>types_;
8
};
9
10
Basis*TypeFactory::create(conststd::string&type)
11
{
12
for(size_ti=0;i<types_.size();++i){
13
if(types_[i].typeString_==type)
14
returntypes_[i].typeTemplate_->clone();
15
}
16
17
returnNULL;
18
}
Bleibt nur noch, dass beim Hochfahren alle Templates registriert werden,
wofür die Factory gleich mal mit einer register Funktion erweitert wird
(Die Factory sollte auch die template Objekte noch im Destructor deleten
und den op= bzw. ihren eigenen CCtor auf private/nicht implementiert
setzen. Man könnte auch gleich ein Singleton Pattern für die Factory
benutzen)
Edit: gerade gesehen. register geht natürlich nicht als Funktionsname.
Da musst du dir was anderes ausdenken
> Und letzte Frage: Wie handhabe ich es, wenn jede> Klasse andere Parameter hat die wiederum der Interpreter nicht> kennen kann?
Indem du allen Klassen eine gemeinsame Funktion gibst, die eine
Argumentliste als Liste übernimmt. Damit haben wieder alle Klassen
dieselbe Schnittstelle, mit der sie befüllt werden.
Da der Interpreter wie du sagst nicht wissen kann, welche Argumente für
eine bestimmte Objektklasse gültig sind, muss sich das Objekt selbst aus
der Argumentliste alles Benötigte extrahieren. Denn nur das Objekt
selbst weiß, was es alles braucht.
Lass dich nicht mit Copy-Konstruktoren oder Create-Methoden verwirren.
Was du willst, ist ganz klassisch eine Factory, die dir die Objekte
anhand des Namens anlegen kann. Und da du anscheinend alle Klassen zur
Uebersetzungszeit kennst, machst du das am einfachsten klassisch mit
einem langen if-elseif-else-Baum der dann eben das jeweilige Objekt mit
new erzeugt oder schneller in der Laufzeit, aber aufwendiger ueber eine
Hashtable, in die du kleine Klassenfactories steckst. Mit ersterer
Variante kannst du auch deine verschiedenen Konstruktorparameter
hinfummeln, mit letzter wohl eher nicht. Vor Allem versuche nie mit
Gewalt eine "saubere" OO-Loesung zu bauen, das wird oft nur unleserlich.
Alex Wurst schrieb:
> ...>> Angenommen, eine Änderung in den Klassen wäre kein Problem, wie geht's> dann weiter? Jede erhält einen CC?
CC ist hier unpassend, weil du ja kein Objekt zum Kopieren hast.
Vielmehr soll ja eines anhand der XML-Datei erzeugt werden.
Deshalb brauchst du die create()-Methoden, die KHB oben skizziert hat
(im Gegensatz zum KHB-Artikel muß die create() aber static sein!).
Die brauchen dann aber noch einen Parameter, anhand dessen sie
wissen, was zu tun ist; beispielsweise einen Zeiger in die Datei,
ab der die Beschreibung steht oder eine C++-Repräsentation des
XML-Knotens. je nachdem wie du liest.
> Und wie "meldet sich eine Klasse an"
Einmal bei Programmstart oder spätestens bei der ersten Verwendung.
Bei Programmstart geht nicht leicht elegant zu lösen.
Man müsste ein statisches Objekt für jede Klasse anlegen, in dessen
ctor die Anmeldung angetriggert wird.
Das ist insofern kritisch, weil das erst geschehen kann, wenn
bereits die Infrastruktur steht, bei der man anmeldet.
Mit etwas Mühe sollte es aber gehen...
> z.B. in einer verketteten Liste, von der es noch gar kein Objekt gibt?
Liste ist hier ganz schlecht, weil sie bei jedem Suchvorgang
durchlaufen werden muß.
Besser ist eine std::map< std::string, Base*(*)(FILE*) > (bzw.
je nach Typ der create()-Methode der Parameter des zweiten
Arguments angepasst), in der vom Typ auf die zugehörige
create()-Methode abgebildet wird.
> Denkfehler meinerseits? Und letzte Frage: Wie handhabe ich es, wenn jede> Klasse andere Parameter hat die wiederum der Interpreter nicht kennen> kann?
Das ergibt sich ganz von selbst, wenn die create() nur einen
Dateizeiger bekommen oder den XML-Knoten wie auch immer und
selbst daraus lesen.
Der Aufrufer braucht gar nicht zu wissen, was da drin steht
und muss auch nichts weiter an Argumenten übergeben.
Klaus Wachtler schrieb:
> CC ist hier unpassend, weil du ja kein Objekt zum Kopieren hast.> Vielmehr soll ja eines anhand der XML-Datei erzeugt werden.> Deshalb brauchst du dir create()-Methoden, die KHB oben skizziert hat> (im Gegensatz zum KHB-Artikel muß die create() aber static sein!).
:-)
Habs schon gesehen :-)
Ich wollte ihn absichtlich nicht in die create-Schiene schicken, damit
nicht auch noch Funktionspointer ins Spiel kommen. Auf der anderen Seite
haben solche Monsterframeworks oft schon einen vCCtor eingebaut.
Auf der anderen Seite wäre das wohl die bessere Lösung gewesen.
ok, zusammen bekommen wir es schon noch hin :-)
Was ich aber vergessen hatte noch zu erwähnen:
Der Aufrufer liest die Datei soweit, bis er den Typ hat.
Dann geht er mit diesem Typ in die map und ruft die
create()-Methode auf, die er dort findet.
Klaus Wachtler schrieb:
> ok, zusammen bekommen wir es schon noch hin :-)>> Was ich aber vergessen hatte noch zu erwähnen:> Der Aufrufer liest die Datei soweit, bis er den Typ hat.> Dann geht er mit diesem Typ in die map und ruft die> create()-Methode auf, die er dort findet.
Jup.
Das erscheint mir ein guter Plan zu sein. Letztenendes verlagert es die
Funktionalität dorthin, wo sie sein soll: Ins Objekt selber.
Einziges Problem: Sein Monsterframework und die Auflage, dass sich da
nicht allzuviel ändern darf. Aber bis jetzt sind die Änderungen ja noch
überschaubar :-)
Wenn allerdings tatsächlich nichts geändert werden darf, dann bleibt nur
der Weg, das alles extern im Interpreter auf die klassische Art zu
machen. Nicht schön, nicht besonders wartbar aber dafür fehleranfällig
:-)
.. also etwas für Masochisten.
Die Änderungen sind sogar sehr übersichtlich, weil pro Klasse
eine create-Methode zu schreiben ist. Der Rest findet ausserhalb
statt.
@Alex: wenn du es nicht elegant hinbekommst, kann ich auch morgen
nachmittag ein komplettes Beispiel mit dem ganzen Kram hinschreiben,
das meiste dazu habe ich fertig in der Schublade.
Heute schaffe ich es aber nicht mehr.
Bei Bedarf bitte Bescheid sagen!
"Es ist noch dazuzusagen, dass ich die abgeleiteten C-Klassen nicht
großartig ändern sollte, es sind über 450 Stück, die von etwa 10
verschiedenen Entwicklern über Jahre hinweg geschrieben wurden."
Ein klares Signal dafuer, nicht in jede Klasse create()-Methoden
einzubauen. Das Test-Framework sollte den bestehenden Code so wenig wie
moeglich veraendern.
naja, muss er halt selber sehen.
Er hat nach einer eleganteren Möglichkeit gefragt; das scheint ihm
ohne Änderungen nicht elegant genug zu sein.
Den Code, die Parameter zu sammeln und gezielt je nach Klasse
etwas damit zu machen, muß er sowieso schreiben - egal ob in
den 450 Klassen oder außerhalb.
Solchen Code, der eigentlich in die Klassen gehört, außerhalb
unterzubringen und dafür ein switch mit 450 cases, würde ich mal
nicht unbedingt als elegant bezeichnen.
Abgesehen davon, daß die cases gar nicht mit Strings für den Typ
gehen.
Alex Wurst schrieb:
> Hallo,> ...> Sonstiges:> Alle Objekte haben eine gemeinsame abstrakte Basisklasse, die> POSIX-Methoden wie READ, WRITE usw. implementiert.> ...
Wovon kann READ denn lesen? Aus einer offenen Datei ab der aktuellen
Stelle?
Klaus Wachtler schrieb:
> Den Code, die Parameter zu sammeln und gezielt je nach Klasse> etwas damit zu machen, muß er sowieso schreiben - egal ob in> den 450 Klassen oder außerhalb.
Ich überleg schon die ganze Zeit, ob man nicht mit dem alten Grundsatz
"Wenn du ein Problem nicht lösen kannst, stülpe eine Klasse drüber"
weiterkommt.
In etwa so:
Wir möchten eine Klasse um 2 Funktionen erweitern, die aber nicht in der
Klasse sein sollen. Also leiten wir von der Klasse eine neue Klasse für
das Testframework ab, welche die beiden fehlenden Funktionen ergänzt.
1
classIODevice:publicBasis// vorhandene Klasse
2
{
3
...
4
};
5
6
classTestIODevice:publicIODevice
7
{
8
public:
9
staticTestIODevice*create();
10
voidinitWithArgs(Arguments);
11
}
Der Testcode arbeitet mit der TestIODevice Klasse, anstelle der IODevice
Klasse.
Da es hier nur um Erzeugung und Initialisierung geht, könnte man
allerdings auch so arbeiten:
1
classIODevice:publicBasis// vorhandene Klasse
2
{
3
...
4
};
5
6
classTestIODevice
7
{
8
public:
9
staticIODevice*create();
10
voidinitWithArgs(IODevice*device,Arguments);
11
}
also quasi eine Klasse, die das unveränderte Objekt erzeugen und
initialisieren kann.
Kein Grund, da mit riesigen if-else Ketten zu arbeiten. Es gibt immer
eine OOP Lösung, die wartbarer, besser erweiterbar und übersichtlicher
ist, als ein if-else-if der über einen Typ läuft.
Karl heinz Buchegger schrieb:
> Kein Grund, da mit riesigen if-else Ketten zu arbeiten. Es gibt immer> eine OOP Lösung, die wartbarer, besser erweiterbar und übersichtlicher> ist, als ein if-else-if der über einen Typ läuft.
Ein Stapel if-else-if ist so leicht lesbar wie eine Tabelle. Und ganz
sicher nicht schlechter lesbar als der Stapel an Factory- oder
Template-Instanziierungen, die du sowieso brauchst. Die
create()-Methoden sind auch die Gleichen, egal ob sie in jeder Klasse
stehen, in jedem Wrapper oder in der einzelnen Testklasse.
OO ist kein Selbstzweck und es gibt ueberhaupt keine Grund, eine
OO-Loesung zu produzieren, wenn eine "prozedurale" Loesung so direkt und
einfach und noch dazu ohne Eingriff in die existierenden Klassen
moeglich ist.
Und es gibt überhaupt keinen Grund, überhaupt OOP zu verwenden,
wenn man es einfach nicht mag :-)
@KHB:
Wenn ich dich richtig verstanden habe, müsste man damit aber
von allen 450 Klassen jeweils eine ableiten?
Klaus Wachtler schrieb:
> Und es gibt überhaupt keinen Grund, überhaupt OOP zu verwenden,> wenn man es einfach nicht mag :-)
Mit moegen hat das nichts zu tun.
Klaus Wachtler schrieb:
> Und es gibt überhaupt keinen Grund, überhaupt OOP zu verwenden,> wenn man es einfach nicht mag :-)>> @KHB:> Wenn ich dich richtig verstanden habe, müsste man damit aber> von allen 450 Klassen jeweils eine ableiten?
Yep.
Aber da die Klassen alle im Grunde gleich aussehen, kann man da sicher
mit dem Präprozessor etwas machen (oder Templates, aber mit Templates
bin ich noch schwach :-)
1
#define WRAPPER(X,Y) \
2
class X \
3
{ \
4
public: \
5
static Y* create() { return new Y; } \
6
void init(Y* obj, Arguments*) {} \
7
}
8
9
// und schon gehts dahin
10
WRAPPER(IODevice,TestIODevice);
11
WRAPPER(UART,TestUART);
12
...
Für die Argument müsste man noch was finden und die Klassenregistrierung
könnte man sicherlich auch noch ins Makro mit einbringen.
Zur Not gibt es mehrere Makros mit unterschiedlicher Parameteranzahl,
wobei die zusätzlichen Parameter die Argumenttypen im init regeln.
Denn besteht der ganze Klassenaufbau im Grunde nur noch aus einer Liste
dieser Makros mit den entsprechenden Argumenten. Und die create Funktion
kann das Objekt, dank std::map, rasend schnell erzeugen, bei der
Registrierung hat man die Gewissheit keine Bezeichnung doppelt verwendet
zu haben (weil die Registrierfunktion das selbstverständlich überprüft)
und eine zusätzliche Device Klasse ist einfach nur ein Eintrag mehr in
dieser Makro-"Tabelle" :-) Herz was willst du mehr!
Man könnte natürlich auch einen Generator schreiben, der diese
Hilfsklassen aus dem XML-File erzeugt. Fällt mir jetzt gerade ein. Hmm.
Das wär gar nicht so dumm. Dann hätte man auch gleich die Kontrolle ob
das XML File einen Fehler enthält. Damit habe ich nämlich immer noch so
meine Bedenken. Da der TO sowieso XML einliest, muss er auch einen XML
Parser haben. Und sobald diese Beschreibung im Speicher ist, ist es ja
ein Klacks, daraus die Klassen zu erzeugen und den Factory-Init Code zu
generieren.
Guten Morgen,
hier hat sich ja einiges verselbständigt ;-) Sehr schön. Ich habe
bereits einen einfachen XML-Parser und zwar TinyXML. Wird bis jetzt
allerdings nur für die Konfiguration der GUI verwendet und könnte die
XML-Objekt-Aufgabe übernehmen.
Die READ, WRITE, usw. sind generische I/O-Funktionen von Treibern.
Dahinter kann sich eine Datei, ein Analogeingang/Ausgang, eine PWM oder
auch ein UART sowie SD-Kartenleser verbirgen. Das weiß ich alles gar
nicht - muß ich auch nicht - solange die Schnittstelle so implementiert
ist wie in der Basisklasse vorgegeben (entspricht teilweise POSIX). Die
Anzahl Parameter ist (außer beim Konstruktor) auch vorgegeben. Ob sich
dahinter jetzt aber ein plumper Integer oder ein Zeiger auf eine
Struktur befindet, wird durch "IoDataRef"-Typedef usw. gekapselt, d.h.
der Interpreter braucht nicht wissen, was es ist.
Die Lösung von KHB's Post vom 21.09.2009 17:41 erscheint mir bis jetzt
am praktikabelsten, denn XML möchte ich vermeiden sofern möglich, ist
aber kein Problem wenn's nötig wäre. Eine Ableitung aller 450 Klassen
kommt für mich nicht in Frage, dann lieber 1-2 Funktionen in jeder
Klasse per "intelligentem" Copy+Paste hinzufügen, das dauert dann halt
mal einen Freitag Nachmittag und eine Rundmail an die Kollegen lang ...
An dem Template-Beispiel hab ich auf jeden Fall Interesse. Vermutlich
werde ich aber erst morgen wieder zu dem Projekt kommen, deshalb wäre
heute Nachmittag kein Problem :)
Vielen Dank erstmal wieder für die - wie immer - sehr kompetente Hilfe
hier!
Wenn du kein XML haben willst, mache ich mir das Leben auch einfacher...
Meine Eingabedatei obj.txt sieht dann so aus (je Zeile ein Typ,
dann die Parameter):
ich habe jetzt nur die ersten 3 von 450 geschrieben...
Jeder Typ kann eigene Typen und Anzahl der Parameter haben.
Wenn die Datei nicht so aussieht, sind entsprechend die
create()-Funktionen anzupassen.
Falls sie gar nicht aus einer Datei lesen, sondern z.B.
aus einer XML-Repräsentation, entsprechend auch ihre
Parameterlisten und passend dazu der zweite Typ der Map
(Zeiger auf die create()).
Kompilieren und ausführen sieht so aus:
Viel Spaß damit!
Was ich natürlich komplett unterschlagen habe ist jedwede
Fehlerbehandlung.
Z.B. können die create() feststellen, daß ihre Parameter nicht passen
und
müssten dann eine Ausnahme werfen o.ä.; aber das ist ein anderes Thema.
Es funktioniert eigentlich genau so wie ich das haben wollte. Hatte noch
versucht die Registrierung irgendwie zu automatisieren, aber nach einer
Stunde Fehlversuchen hab ich das dann gelassen. So dürfte das okay sein,
es ist eine Collage aus KHB's und Ihrem Code.
Ich habe das create() noch etwas "aufgebohrt" in dem es eine va_list
akzeptiert. Dem Interpreter ist es also völlig egal wieviel Parameter
von welchem Typ, der übergibt das halt. Jetzt muß halt jede abgeleitete
Klasse das statische create() implementieren um mitspielen zu dürfen,
ansonsten ist sie zwar verwendbar aber mit dem Interpreter nicht
ansprechbar.
Vielen Dank nochmal, hat mir sehr geholfen!
Alex Wurst schrieb:
> Vielen Dank nochmal, hat mir sehr geholfen!
Ich denke das wichtigste was du mitnehmen sollst, ist die Technik die
dahintersteckt. In einer OOP Welt nenn man so etwas ein
'Design-Pattern'. Klar hätte man auch eine ordinäre if-else-if machen
können. OOP kann nichts, was man nicht mit den althergebrachten Mitteln
auch erreichen könnte. Aber oftmals sind die OOP Mitteln universeller.
Wenn du zb in Zukunft ein Programm brauchst, das über die Fähigkeit
verfügt zur Laufzeit zusätzliche Objekte einzubinden (auch dann wenn sie
in einer DLL stecken), dann kennst du jetzt eine OOP Methodik mit der
man das 'simpel' machen und organisieren kann: Objekte, die sich bei
einer Factory registrieren. Wie das dann konkret aussieht (templatre
Objekt, create Funktion, ...) ist nicht der springende Punkt. Der
springende Punkt ist, dass es eine zentrale Stelle gibt, die über eine
standarddisierte und dem Objekt vorgeschrieben Schnittstelle ein Objekt
erzeugen kann.