Frage zu Design, oder: Zirkulär in den Fuß geschossen



  • Servus,

    ich habe mir heute irgendwie ein architektonisches Loch in den Fuß geschossen 😞
    Und zwar habe ich mir eine zirkuläre Abhängigkeit gebaut und eine Alien Spider eingefügt. Nix gut.

    Zum Problem:
    Es handelt sich um eine Library, die auf UDP schreibt und lauscht und dabei ein bestimmtes Protokoll sprechen kann. Sie wird in zwei verschiedene Projekte gelinkt, wobei eines Qt benutzt, das andere nicht.
    Daher verwende ich ein abstraktes Interface für die UDP-Verbidnung, was einmal konkret durch ASIO realisiert wird, und einmal konkret durch QUDPSocket.

    Das Interface sieht so aus:

    class NetworkConnector{
    public:
        virtual ~NetworkConnector() {}
        virtual void send(const void* msg, std::size_t bytes) = 0;
        virtual void receiveCallback(void* msg, std::size_t bytes_recvd) = 0;
    };
    

    Wobei send() eine Funktion ist, die "von vorne" ausgerufen wird, wenn man was via UDP rausschreiben will, receiveCallback() hingegen wird "von hinten" asynchron aufgerufen, wenn frische Daten ankommen.

    Jetzt gibts die Klasse, die sich um das gesprochene Protokoll kümmert. Die hängt ab vom verwendeten Datenformat, das hier "Paket" heißt.

    Das Interface sieht so aus:

    class ProtocolConnector
    {
    public:
        virtual ~ProtocolConnector() {}
        virtual void send(const Paket& msg) = 0;
        virtual void receiveMessage(const Paket& msg) = 0;
    };
    

    Man beachte, hier gibts keine void* mehr, hier steht der Datentyp dann fest. Auch hier ist receive-Funktion wieder ein Callback, das asynchron aufgerufen wird.

    Das Problem ist jetzt:
    Der konkrete NetworkConnector braucht eine Referenz auf den konkreten ProtocolConnector, schließlich muss er ja dessen receiveMessage()-Funktion aufrufen können, wenn er Daten über seine receiveCallback()-Funktion bekommt.
    Andererseits muss aber der konkrete ProtocolConnector eine Referenz auf den konkreten NetworkConnector haben, schließlich muss er ja irgendwem die formatierten Daten geben, die er rausschieben will.

    Die konkreten Klassen benötigen sich also gegenseitig.

    Der konkrete Anwendungsfall sieht also in etwa so aus:

    class Anwendung {
    public:
        Anwendung(const std::string& host_addr,
                  int incoming_port,
                  int outgoing_port):
        m_protocol_connector(&m_network_connector),
        m_network_connector(&m_protocol_connector, host_addr, incoming_port, outgoing_port)
        {}
    
    protected:
        ConcreteProtocolConnector m_protocol_connector;
        ASIOConnector m_network_connector;
    };
    

    Der konstruktor zeigt ja ganz klar, wo ich Bauschmerzen habe: Ich gebe dem ConcreteProtocolConnector einen Zeiger auf einen noch nicht vorhandenen Speicherbereich, in dem der konkrete NetworkConnector erstellt wird (hoffentlich). Das funktioniert, aber ist das undefiniertes Verhalten?

    Das kompiliert, linkt und funktioniert augenscheinlich, aber ich habe Bauchschmerzen dabei.

    Hat irgendjemand eine bessere Idee, wie ich das mit weniger Abhängigkeiten in den Griff bekomme?

    Phil



  • Warum receiveCallback()? Frag doch einfach ab, ob/bis was da ist.



  • Weniger Abhängigkeit nicht, aber:

    class Anwendung {
    public:
        Anwendung(const std::string& host_addr,
                  int incoming_port,
                  int outgoing_port)
        {
            m_protocol_connector = new ConcreteProtocolConnector();
            m_network_connector = new ASIOConnector();
    
            m_protocol_connector->init(m_network_connector);
            m_network_connector->init(m_protocol_connector, host_addr, incoming_port, outgoing_port);
        }
    
    protected:
        ConcreteProtocolConnector *m_protocol_connector;
        ASIOConnector *m_network_connector;
    };
    

    Du musst dann natürlich noch entweder Smart Pointer verwenden (bietet sich ja an wenn du sowieso boost benutzt) oder den Speicher im Dekonstruktor wieder freigeben.

    Zugegebenermaßen ist das ganze natürlich etwas aufwendiger aber dafür verhält es sich definiert, das was du bisher hast, da müsste mal einer von den Standard Profis was zum definierten oder eben nicht definierten Verhalten sagen (ich tippe aber auf nicht definiert).


Anmelden zum Antworten