Designproblem



  • Hey,

    mich beschäftigt seit einigen Tagen ein Designproblem.

    Vereinfacht sieht es so aus, das ich eine Klasse Socket habe:

    class Socket
    {
    protected:
     int sock;
     SocketAddr addr; // Klasse SocketAddr enthält sockaddr_in
    
    public:
     Socket();
     ~Socket();
    
     void close();
    
     int& getRawSocket() { return sock; }
    };
    

    Alles natürlich kein Problem.

    Damit ich jedoch Verbindungen annehmen kann, brauche ich noch eine Klasse Listener. Die Verbindung wird duch ein Objekt der Klasse Client dargestellt. Dort werden später noch weitere Informationen zu der Verbindung gespeichert (Verbindungsdauer, Loginstatus, etc.).

    Da habe ich dann auch zwei Möglichkeiten:
    Ich lasse Client und Listener von Socket erben. Aber kann man sagen "Ein Client/Listener ist ein Socket"?

    Oder ich löse es über Komposition und sage: "Ein Client/Listener hat ein Socket"?

    Das ist momentan die erste Frage, die mich quält... Ich kann mich nicht entscheiden, da sich beides für mich richtig anhört.

    Doch das wirkliche Problem ist die Funktion accept der Klasse Listener. Ich muss nämlich auf sock und addr zugreifen, da diese verändert werden, wenn eine Verbindung angenommen wird.

    Ich müsste sie also so realisieren, falls ich das über Komposition löse:

    void Listener::accept(Socket& newSock)
    {
     newSock.getRawSocket() = accept(sock.getRawSocket(),(sockaddr_in *) &newSock.getAddr(), &newSock.getAddr().getLength());
    }
    

    Empfinde ich als sehr hässlich und der Client müsste auch eine Funktion getSock() zur Verfügung stellen, die ein Socket& zurückgibt:

    // ...
    Client newClient;
    listener.accept(newClient.getSocket());
    

    Ich könnte auch einfach ein Client-Objekt vom Listener annehmen lassen, aber ich möchte flexibel bleiben.

    Bei Vererbung fällt ein get-Aufruf raus, da ich auf die Member des Listener direkt zugreifen kann (protected):

    void Listener::accept(Socket& newSock)
    {
     newSock.getRawSocket() = accept(sock,(sockaddr_in *) &newSock.getAddr(), &newSock.getAddr().getLength());
    }
    

    Alles in allem ist es aber nicht viel besser und noch immer unschön gelöst. Mich macht das schon so richtig traurig, dass ich da seit Tagen keine schönere Lösung finden kann. 😞

    Muss ich das tatsächlich so hinnehmen oder kann man das nicht besser machen? Ich möchte mich ja bei meinen OO Designs auch verbessern.



  • Ich würde Listener::accept() einen Socket-Pointer zurückgeben lassen.



  • Janjan schrieb:

    Ich würde Listener::accept() einen Socket-Pointer zurückgeben lassen.

    In dem Fall müsste ich zwangsläufig mit new rummachen, was mir eigentlich auch misfällt. Da würde ich sogar den hässlichen Code bevorzugen, da so keine Speicherlöcher entstehen können.



  • Ich habe es so gelöst, dass meine Socket-Klasse ein möglichst einfacher Wrapper um SOCKET herum ist.

    struct Socket : public noncopyable
    	{
    		typedef int Handle;
    
    		Handle handle;
    		bool isEvent;
    
    		Socket();
    		~Socket();
    
    		bool isConnected() const;
    		bool bind(uint port);
    
    		bool connect(const char *host, unsigned short port);
    		bool accept(Socket &client, std::string *out_ipAddress = 0);
    		void close();
    
    		bool send(const void *data, size_t size);
    		bool receive(void *out_data, size_t size, size_t &out_received);
    	};
    

    Weiteres, wie Unterscheidung in Listener/Client, findet auf der nächsten Ebene statt, beispielsweise so:

    struct Listener
    {
    	bool listen(unsigned short port);
    	bool isClient() const;
    	Client *accept();
    
    private:
    
    	Socket m_socket;
    };
    
    struct Client
    {
    	Socket socket; //für einfachen Zugriff (z. B. durch Listener)
    
    	void close();
    	bool send(..);
    	bool receive(..);
    };
    

Anmelden zum Antworten