Klassendesign: IRC Client Library



  • Hallo Leute,

    Ich bastle gerade an einer IRC-Client Library, komme allerdings auf keinen gruenen Zweig, wie ich das ganze designen soll. Derzeit habe ich eine Klasse irc::socket, der von irc::session verwendet wird, aber mit dem der User nicht in Beruehrung kommen soll. irc::session hat Methoden um Channels zu joinen, Nachrichten zu senden, etc und haelt die Session-Daten, wie Nickname, Channel-Liste, etc.

    Der User kann bei irc::session Callbacks angeben, die dann fuer ihn aufgerufen werden, sobald das entsprechende Event eintritt, z.B.:

    irc::server_data server_data("irc.f00.net", 6667);
    irc::session_data session_data("f00bar");
    irc::session session(server_data, session_data);
    
    session.on_connect([&] { session.join("#f00"); });
    
    session.on_join
    (
    	[&] (irc::join_event const& e)
    	{
    		if(e.user().nickname != session_data.nickname)
    			session.message(e.channel(), "Welcome " + e.user().nickname + " to " + e.channel() + "!");
    	}
    );
    
    session.run();
    

    Irgendwie gefaellt mir das ganze ueberhaupt nicht. irc::session mutiert zu einer Gottklasse mit zig Funktionen, fuer jede Art von Aktion eine, und dazu ein Event-Handler, sowie eine Event-Registrierungsfunktion.

    Ich wuerde gerne das Event-Handling von der Session irgendwie entkoppeln, aber das braucht ja indirekt auch das Protokoll, welches den Socket der Session braucht.

    Hat jemand einen besseren Vorschlag?

    Gruesse,
    Der Kellerautomat



  • *push*



  • Wie wäre folgendes:

    Du hast eine Methode
    addEventListener(event, function);

    Und wenn jetzt ein Event abgefeuert wird, muss nur die liste der EventListener durchgegangen werden und das assoziierte Event verglichen werden.

    So ist die Session Klasse unabhängig von den Events und kann auch custom events feuern wenn client code das verlangt. Und man kann unendlich viele listener auf ein event haben.

    PS:
    Was ich meine ist folgendes (Pseudo Code):

    //vom client aufgerufen
    void Session::addEventListener(Event evt, Function func) {
       listeners.add({evt, func});
    }
    
    //intern aufgerufen, zB bei einem join
    void Session::fireEvent(Event evt) {
       for(auto obj : listeners) {
         if(obj.evt == evt) obj.func(evt);
       }
    }
    
    //Client Code wäre dann:
    session.addEventListener(Event.CONNECT, ([&](irc::event const& e) { session.join("#f00"); }));
    session.addEventListener(Event.JOIN,
         [&] (irc::event const& e) 
         { 
             if(e.user().nickname != session_data.nickname) 
                 session.message(e.channel(), "Welcome " + e.user().nickname + " to " + e.channel() + "!"); 
         } 
    );
    

    Edit:
    Event Parameter eingefügt



  • Das sieht zwar grundsaetzlich gut aus, aber wie wuerden dann die Events funktionieren? Du hast hier ja nur einen Event-Typ, wie funktioniert das mit den verschiedenen Event-Arten?



  • Kellerautomat schrieb:

    Das sieht zwar grundsaetzlich gut aus, aber wie wuerden dann die Events funktionieren? Du hast hier ja nur einen Event-Typ, wie funktioniert das mit den verschiedenen Event-Arten?

    Was ich hier skizziert habe ist etwa das, wie ActionScript 3 das event Handling macht.

    Du kannst ruhig mehrer events haben.

    Event könnte zB so aussehen:

    class Event {
    private:
      EventData* data;
      EventType type; //eigentlich nur ein integer!
    
    public:
      Event(EventType type, EventData* data=0)
      : type(type), data(data) {}
    
      ~Event() { delete data; }
    
      bool isEqual(Event const& other) {
        return type == other.type;
      }
    
      EventData const* getData() const {
        return data;
      }
    };
    

    Wenn ich nun ein Join Event haben will, amche ich das so:

    class EventData {
    private:
      Time time;
    public:
      EventData(Time time) : time(time) {}
      virtual ~EventData() {}
      Time getTime() const { return time; }
    };
    class JoinEventData : publuc EventData {
    private:
      Channel chan;
    public:
      JoinEventData(Time time, Channel const& chan) : EventData(time), chan(chan) {}
      Channel const& getChannel() const { return channel; }
    };
    

    und dann mache ich in Session::join folgendes:

    fireEvent(Event(EventType::JOIN, new JoinEventData(getTime(), channel));
    

    PS:
    und im Client Code dann:

    session.registerEventListener(EventType::JOIN, ([&](Event evt) {
      JoinEventData const* data=static_cast<JoinEventData const*>(evt.getData());
      cout<<"Joined Channel: "<<data.getChannel();
    }));
    


  • Das haette dann aber den Nachteil, dass Client-Code explizit auf den richtigen Eventtyp casten muss. Evtl. koennte ich dann einfach addEventListener zu einem template machen, dass eine Trampolinfunktion verwendet, um auf den richtigen Typ zu casten.



  • Kellerautomat schrieb:

    Das haette dann aber den Nachteil, dass Client-Code explizit auf den richtigen Eventtyp casten muss. Evtl. koennte ich dann einfach addEventListener zu einem template machen, dass eine Trampolinfunktion verwendet, um auf den richtigen Typ zu casten.

    Finde ich nicht so toll.

    Du könntest EventData einfach so definieren, dass es immer die selben Felder sind. Ist halt unflexibler, aber du könntest sagen, jedes event hat eben einen channel, eine uhrzeit, einen sender, etc.

    Ich finde das mit dem casten ganz angenehm - da du plötzlich komplett frei bist, was die events betrifft. Session muss nichts wissen. Und in vielen Event Listener braucht man die Event Daten ja auch garnicht. Wenn du das Casten nicht willst, mach mit einer Umwandlungs Funktion die das Casten übernimmt.

    zB könnte getData eine Template Funktion sein:

    JoinEventData const* data=evt.getData<JoinEventData>();
    

    Oder getData liefert ein Proxy Objekt dass einen template operator T* für den Cast hat.

    Das coole an dem System ist eben:
    Du kannst jederzeit neue Events hinzufügen ohne Code ändern zu müssen. Und vorallem: du kannst User Events erlauben.

    Ich kann zB ein Plugin schreiben dass bei jeder Nachricht in einem Channel auf meinen Namen hört und wenn mein Name vorkommt dann feure ich ein Event MyNameWasMentioned ab.

    Fixe Events vorauszusetzen ist unflexibel.



  • Die Events werden doch von session erzeugt und verteilt? Wie soll ich da User-Events erlauben?



  • Kellerautomat schrieb:

    Die Events werden doch von session erzeugt und verteilt? Wie soll ich da User-Events erlauben?

    Ich dachte du wolltest das entkoppeln?

    Session muss dann ja alles checken und überwachen. Ich würde zB Session nur ganz wenige Events anbieten lassen. zB MessageReceived. Und darauf hören dann viele Listener. zB einer der Joins von anderern Usern abfängt und dann eben ein UserJoined Event triggert.

    Wenn Session selber alles handeln soll, dann ist es ein gott objekt 😉

    Aber es reicht eine minimale Funktionalität anzubieten - wie eben MessageReceived und vielleicht ActionReceived und der Rest wird von Listenern gemacht.

    So kannst du ohne Probleme dann so sachen wie einen bad-word filter einbauen, etc.



  • Wollte ich auch, aber dafuer habe ich bisher keine Idee...



  • Kellerautomat schrieb:

    Wollte ich auch, aber dafuer habe ich bisher keine Idee...

    Ansonsten schau dir einfach GUI Frameworks an - die haben alle genau diese Problematik (nur etwas komplexer) und lösen sie auf gewisse Weise.



  • Shade Of Mine schrieb:

    //Client Code wäre dann:
    session.addEventListener(Event.CONNECT, ([&](irc::event const& e) { session.join("#f00"); }));
    session.addEventListener(Event.JOIN,
         [&] (irc::event const& e) 
         { 
             if(e.user().nickname != session_data.nickname) 
                 session.message(e.channel(), "Welcome " + e.user().nickname + " to " + e.channel() + "!"); 
         } 
    );
    

    Was macht das "[&]"? ich seh das zum ersten mal




Anmelden zum Antworten