Ik zit al een tijdje te pielen met het volgende probleem. Ik probeer een simpele parser te maken (voor de botwar, maar dat is verder niet erg relevant) die strings omzet in objecten. Ik krijg bijvoorbeeld de string "USER foo bar" binnen, die wil ik parsen naar een klasse 'UserMessage' met members 'user' en 'password'. Omgekeerd wil ik ook objecten kunnen terugzetten in een string. Een soort serialization dus, maar met simpele ascii strings. Ik heb hiervoor een abstracte klasse voor berichten gemaakt:
Voor elk mogelijk bericht definieer ik een subklasse van message met een bijbehorende toString(), zoals de volgende klasse:
Ik kan nu op makkelijke wijze een UserMessage maken en deze converteren naar een string. Ik kan ook gemakkelijk het protocol aanpassen door nieuwe klasses te maken. Alleen twijfel ik nu hoe ik het parsen van strings moet aanpakken. Ik heb een mooie tokenizer en het parsen opzich is niet zo moeilijk. Maar wat is de beste manier om het te integreren in mijn project?
Op dit moment heb ik een statische functie voor het parsen. Op het eerste gezicht handig, ik doe Message::parse("USER foo bar") en er wordt een UserMessage object geretourneerd. Maar ik zit met het volgende probleem: als ik een nieuw type message toevoeg, moet ik een functie in de abstracte klasse gaan veranderen. Dat is niet erg OO dunkt me, bovendien maakt het het uitbreiden van het protocol lastig.
Ik heb geprobeerd om voor elke subklasse een aparte parse-functie te maken. Maar nog steeds moet ik de abstracte klasse aanpassen, want die moet nu voor elke afgeleide klasse de parse-functie aanroepen. Bovendien zorgt dit voor erg slechte performance, omdat voor elke afgeleide klasse wordt geprobeerd om de gehele string te parsen. In het ideale geval zou dit parallel gebeuren, zodat het parsen meteen stopt bij een ongeldige string.
Een andere oplossing zou zijn om voor elke Message klasse een MessageParser te maken en deze te registreren bij de abstracte klasse. Maar dit lijkt me overkill, bovendien is het slecht voor de performance, want nog steeds moeten alle parse-functies geprobeerd worden. Bovendien komt iets simpels als het parsen van een double steeds terug, dat zou eigenlijk in een centrale functie gedefinieerd moeten worden.
Op dit moment hou ik het maar bij de statische parse-functie. Het is lelijk, maar het werkt! Maar er is vast een betere methode, ik heb het gevoel dat ik in de verkeerde richting zoek. Elke message heeft een uniek keyword, zoals 'USER' in "USER foo bar". Misschien kan ik hier iets mee? Heb je een geheel ander idee, prima, alle hulp is welkom. Maar kom a.u.b. niet met "het is overkill voor die botwars contest, gebruik gewoon wat simpele hacks" (hoewel je misschien gelijk hebt
). Het gaat me meer om het achterliggende idee, hoe pak je zoiets handig aan?
C++:
1
2
3
4
5
| class Message { public: virtual QString toString() const = 0; static Message * parse(const QString & line); }; |
Voor elk mogelijk bericht definieer ik een subklasse van message met een bijbehorende toString(), zoals de volgende klasse:
C++:
1
2
3
4
5
6
7
| class UserEnterMessage : public Message { public: QString toString() const; // getters en setters even weggelaten int turnID; QString user; }; |
Ik kan nu op makkelijke wijze een UserMessage maken en deze converteren naar een string. Ik kan ook gemakkelijk het protocol aanpassen door nieuwe klasses te maken. Alleen twijfel ik nu hoe ik het parsen van strings moet aanpakken. Ik heb een mooie tokenizer en het parsen opzich is niet zo moeilijk. Maar wat is de beste manier om het te integreren in mijn project?
Op dit moment heb ik een statische functie voor het parsen. Op het eerste gezicht handig, ik doe Message::parse("USER foo bar") en er wordt een UserMessage object geretourneerd. Maar ik zit met het volgende probleem: als ik een nieuw type message toevoeg, moet ik een functie in de abstracte klasse gaan veranderen. Dat is niet erg OO dunkt me, bovendien maakt het het uitbreiden van het protocol lastig.
Ik heb geprobeerd om voor elke subklasse een aparte parse-functie te maken. Maar nog steeds moet ik de abstracte klasse aanpassen, want die moet nu voor elke afgeleide klasse de parse-functie aanroepen. Bovendien zorgt dit voor erg slechte performance, omdat voor elke afgeleide klasse wordt geprobeerd om de gehele string te parsen. In het ideale geval zou dit parallel gebeuren, zodat het parsen meteen stopt bij een ongeldige string.
Een andere oplossing zou zijn om voor elke Message klasse een MessageParser te maken en deze te registreren bij de abstracte klasse. Maar dit lijkt me overkill, bovendien is het slecht voor de performance, want nog steeds moeten alle parse-functies geprobeerd worden. Bovendien komt iets simpels als het parsen van een double steeds terug, dat zou eigenlijk in een centrale functie gedefinieerd moeten worden.
Op dit moment hou ik het maar bij de statische parse-functie. Het is lelijk, maar het werkt! Maar er is vast een betere methode, ik heb het gevoel dat ik in de verkeerde richting zoek. Elke message heeft een uniek keyword, zoals 'USER' in "USER foo bar". Misschien kan ik hier iets mee? Heb je een geheel ander idee, prima, alle hulp is welkom. Maar kom a.u.b. niet met "het is overkill voor die botwars contest, gebruik gewoon wat simpele hacks" (hoewel je misschien gelijk hebt
[ Voor 25% gewijzigd door writser op 14-11-2005 17:49 ]
Onvoorstelbaar!