Designfrage



  • Hallo Community!

    Ich habe eine Klasse "Data", die die wichtigsten Daten hält. Des Weiteren gibt es eine Serverklasse "Server", die Request von anderen Netzwerkteilnehmern beantworten soll.

    Ich frage mich, wie ich am elegantesten folgendes Szenario löse:

    Angenommen der Server bekommt einen Requet. Nun muss er irgendwie die "Data" Klasse fragen.

    1. Möglichkeit: Ich übergebe dem Server im Konstruktor eine Referenz auf die Data Klasse. Somit kann dieser im weiteren Verlauf auf diese zugreifen.

    2. Möglichkeit: Ich lasse die Data-Klasse zum Listener des Servers werden. Sobald der Server einen Request erhält, löst dieser ein Event aus, das die "Data" Klasse zu interpretieren weiß.

    Vorteil der 2. Möglichkeit: Ich lasse den Server allgemeiner. Eine Data-Instanz im Konstruktor schränkt sein Einsatzgebiet sehr ein.

    Nachteil der 2.Möglichkeit: Zwar weiß jetzt Data durch ein getriggertes Event, dass er Daten haben will, aber wie antwortet Data darauf?

    Habt ihr ne Idee?



  • Die Frage ist etwas zu allgemein. Generell ist es immer gut, sowas zu entkoppeln. Variante 1 hört sich unflexibel an, aber wenn du genau weißt, dass es reicht, dann spricht auch nichts dagegen.
    Listener gefallen mir an der Stelle nicht so ganz. Das Rausschreiben wär jetzt kein Problem, dazu könnte dein Listener sowas implementieren:

    void onQuery(const Request& request, Response& reponse);
    

    Der Server übergibt ein Response Objekt, in das deine Data Klasse schreiben kann. Aber wirklich schön finde ich das auch nicht. Ich würde eher ein Konzept vorsehen, bei dem man Server Module registrieren kann, die sich um bestimmte Requests kümmern. Das Dispatching könnte man evtl. über einen Service Activator machen.



  • Hey, danke für die Antwort. Ja, irgendwie artet die Angelegenheit bei mir in Gefrickel aus. Daher habe ich diesen Thread gestartet, damit ich sowas gleich zu Beginn vermeiden kann.

    Die Frage ist in der Tat allgemein gehalten, da ich dieses konkrete Problem noch nicht implementiert habe. Fest ist bis jetzt nur, dass ich eine "Data" Klasse und eine "Server" Klasse habe. Die Frage ist nur, wie diese am besten kommmunizieren.

    Könntest Du dein angesprochenes Konzept mit der Modulregistrierung noch etwas erläutern? Ich bin offen für neue (bessere) Ansätze.

    Vielen Dank



  • Was ich geschrieben habe war auch recht allgemein. Konkrete Umsetzungen könnten sehr unterschiedlich aussehen. Eine einfach Variante wäre z.B. eine Basisklasse für Module zu definieren, die z.B. eine virtuelle handleRequest Methode hat. Der Server bietet dann eine Möglichkeit, solche Module reinzuhängen. Wenn das Protokoll HTTP ist, könnte man z.B. über den Pfad entscheiden, welches Modul zuständig ist.



  • gelöscht



  • Also meine Data-Klasse würde diese Basisklasse implementieren? Meinst Du sowas (eher Pseudocode ;))?

    class Base {
    public:
      virtual Response handleRequest(Request);
    };
    
    class Data : Base {
    
    public: 
       virtual Response handleRequest(Request r){
        // zauberrei
        return ergebnis;
      }
    };
    
    class Server {
      vector<Base> dataSources;
      public void AddDataSource(Base b) {
         dataSources.push_back(b);
      }
    
      //...
    
      private void HandleClient(TcpClient client) {
          Request r =  fetch_request();
          Response res = dataSources[richtiger_index].handleRequest(r);
          client.send(res);
      }
    
    };
    
    // ....
    Data d;
    Server s;
    
    s.AddDataSource(d);
    

    xxx933



  • Nein. Deine Datenklasse soll nur Daten halten und sich nicht um irgendwelche IO kümmern.
    Der Request enthält irgendeine Information, über der er sein Anliegen dem Server mitteilt. Ob das ein Schlüsselwort oder ein numerische Konstante ist spielt keine Rolle. Abhängig von diesem Schlüsselwort wird eine Methode/Function Object/was auch immer aufgerufen, das sich um die Anfrage kümmert.

    Pseudocode:

    void Server::on_telegram( Telegram& telegram )
    {
       ITelegramHandler* Handler = TelegramHandlerFactory.create_handler( telegram.RequestType );
       Handler->handle_telegram( *this, telegram );
    }
    


  • Genau. Die Zuständigkeiten immer trennen, Separation of Concerns. Wenn sich deine "Data" Klasse (der Name hört sich ehrlich gesagt etwas komisch an, ist da auch Business Logik drin?) auch noch um Kommmunikation kümmern soll, passt es irgendwie gar nicht dazu. Und wenn du dann noch eine andere Art der Kommunikation hinzufügst (oder Statistik, Logging, Rechtemanagement), soll sich die Klasse da auch noch drum kümmern und immer weiter aufgebläht werden? Besser kleine Komponenten, die nur eine Aufgabe haben und die man wiederverwenden kann.



  • Vielleicht solltest du bei einer reinen Daten- und Serverklasse bleiben und eine neue Funktion schreiben, die die beiden zusammen bringt, anstatt zu überlegen, ob die Serverklasse Daten verarbeiten können oder die Datenklasse mit Netzwerkverbindungen umgehen können soll.



  • DocShoe schrieb:

    Nein. Deine Datenklasse soll nur Daten halten und sich nicht um irgendwelche IO kümmern.

    Tut sie das denn bei mir? Sie holt die Daten und gibt sie dann über die vom Server aufgerufene HandleRequest Methode zurück. Oder meinst Du das Zurückgeben sei schon IO? Irgendwie müssen die Datan von "Data" ja zum Server gelangen.

    DocShoe schrieb:

    void Server::on_telegram( Telegram& telegram )
    {
       ITelegramHandler* Handler = TelegramHandlerFactory.create_handler( telegram.RequestType );
       Handler->handle_telegram( *this, telegram );
    }
    

    Noch ganz blicke ich durch die Idee nicht durch. Angenommen der Server möchte Daten haben. Dann ruft er "on_telegram auf"?! Was genau ist der Parameter? Und wie kriegt "Data" vom Request mit bzw reicht die Daten zurück?

    Mechanics schrieb:

    Genau. Die Zuständigkeiten immer trennen, Separation of Concerns.

    Das ist mir auch sehr wichtig. Ich könnte mir schon was zusammenfrickeln, aber möchte ich es möglichst gut machen 😉

    Mechanics schrieb:

    Wenn sich deine "Data" Klasse (der Name hört sich ehrlich gesagt etwas komisch an, ist da auch Business Logik drin?).

    Nein. Das ist auch eher ein bisschen exemplarisch, weil ich durch den Namen "Data" das Problem hervorheben wollte.

    nwp3 schrieb:

    Vielleicht solltest du bei einer reinen Daten- und Serverklasse bleiben und eine neue Funktion schreiben, die die beiden zusammen bringt, anstatt zu überlegen, ob die Serverklasse Daten verarbeiten können oder die Datenklasse mit Netzwerkverbindungen umgehen können soll.

    Das sollen sie ja gerade nicht. Die Datenklasse soll sich ausschließlich um die Bereitstellung von Daten kümmern und diese bei Anfrag an den Server zurückgeben, der sich wiederum ausschließlich um Netzwerkverbindungen kümmert.



  • Wir meinen im Gunde alle dasselbe. Die zusätzliche "Funktion", von der nwp3 schreibt ist ja auch nichts anderes, als das was ich oder DocShoe vorgeschlagen haben.
    on_telegram wäre eine interne Funktion vom Server, die aufgerufen wird, wenn eine Anfrage reinkommt. Wie auch immer das funktionieren soll, z.B. über Sockets.
    ITelegramHandler wäre eine Schnittstelle, die der Server definiert und die du irgendwo implementierst. z.B. in einer Klasse DataTelegramHandler, die deine Data Klasse kennt. Das wäre die zusätzliche Schicht, die dafür sorgt, dass Server und Data sich nicht kennen.


Log in to reply