Risoluzione della dipendenza circolare tra due classi

4

Sto cercando di risolvere una dipendenza circolare tra due componenti nel mio sistema. Il componente Messenger è responsabile per l'invio e la ricezione di messaggi su un socket Web. Il componente Controller richiede l'utilizzo del componente Messenger per poter inviare messaggi ai client connessi perché il componente Messenger è a conoscenza delle sessioni connesse.

Il componente Messenger ha una dipendenza dal componente Controller per poter notificare al controller i messaggi in arrivo.

Il WebsocketHandler è un'interfaccia framework su cui non ho alcun controllo e in questo caso chiama i metodi appropriati sulla mia implementazione in base ai client connessi.

Quali modifiche architettoniche posso apportare per risolvere questo problema?

interface WebsocketHandler
{
    void onConnect(Session session);
    void onDisconnect(Session session);
    void recieve(Object payload);
}

class Messenger implements WebsocketHandler
{
    Controller controller;
    Set<WebsocketSession> websocketSessions;

    @Override
    void onConnect(Session session)
    {
        sessions.add(session);
    }

    @Override
    void onDisconnect(Session session)
    {
        sessions.remove(session);
    }

    @Override
    void recieve(Object payload)
    {
        controller.messageReceived(payload);
    }

    // requires access to sessions to send messages
    void send(Object payload)
    {
        for (WebsocketSession session : websocketSessions)
        {
            session.send(payload);
        }
    }
}

class Controller
{
    Messenger messenger;

    void messageReceived(Object payload)
    {
        // do something with payload
    }

    void notifyClients()
    {
        messenger.send(...);
    }
}
    
posta Robert Hunt 09.08.2018 - 15:32
fonte

3 risposte

6

La soluzione semplice a questo è riconoscere che Controller non ha bisogno di una dipendenza su Messenger , ha bisogno di una dipendenza su send . Quindi forniscici quella dipendenza:

interface Sender {
    void send(Object payload);
}

class Messenger implements WebsocketHandler, Sender { …

class Controller
{
    Sender sender;

    void messageReceived(Object payload)
    {
        // do something with payload
    }

    void notifyClients()
    {
        sender.send(...);
    }
}

Ora, Controller ha solo una dipendenza dall'interfaccia Sender , rimuovendo quella dipendenza circolare.

Aggiorna

Come sottolinea l'OP, quanto sopra rimuove la dipendenza circolare diretta tra Controller e Messenger . Tuttavia, se viene utilizzata l'iniezione del costruttore, la dipendenza esiste ancora al momento della creazione degli oggetti: dobbiamo prima creare Sender per soddisfare il fabbisogno di Controller di Sender , ma dobbiamo creare Controller prima come Messenger deve chiamare controller.messageReceived(payload) .

Ci sono vari modi per aggirare questo problema, come il supporto dell'iniezione di proprietà. Ma il mio preferito personalmente, per le lingue che supportano le funzioni di cittadini di prima classe, è quello di disaccoppiare completamente i due oggetti tramite le funzioni.

Una soluzione del genere, espressa come C # (come ho più familiarità con quella lingua), potrebbe essere simile a:

class Messenger : WebsocketHandler
{
    private readonly Action<object> _messageReceived;
    private readonly IEnumerable<WebsocketSession> _websocketSessions;

    public Messenger(Action<object> messageReceived) 
        => _messageReceived = messageReceived;


    public void Receive(object payload) => _messageReceived(payload);

    public void Send(Object payload)
    {
        foreach (var session in _websocketSessions)
        {
            session.Send(payload);
        }
    }
}

class Controller
{
    private readonly Action<object> _send;

    public Controller(Action<object> send) => _send = send;

    public void MessageReceived(object payload)
    {
        // do something with payload
    }

    public void NotifyClients() => _send(null);
}

E quindi il codice per accoppiare i due oggetti in fase di esecuzione potrebbe essere qualcosa del tipo:

void Setup()
{
    Controller controller = null;
    var messenger = new Messenger(MessageReceiver);
    controller = new Controller(Sender);

    void MessageReceiver(object payload) => controller.MessageReceived(payload);
    void Sender(object payload) => messenger.Send(payload);
}

Java in questi giorni supporta anche questo approccio. Non so però quanto bene funzionerebbe con framework IoC come Spring.

    
risposta data 09.08.2018 - 17:06
fonte
1

The Messenger component has a dependency on the Controller component to be able notify the controller of incoming messages.

OMG java non ha eventi !!!

Tuttavia ha diverse alternative. Ad esempio

link

Qui realizzerai il tuo Messenger implementare Observable e avere la chiamata Controller addObserver . Il Messenger può quindi notificare al Controller i messaggi in arrivo con la sua funzione notifyObservers() senza dover fare riferimento alla classe Controller.

Ci sono diverse varianti sulla tecnica.

link

L'idea è di passare al messenger la funzione da chiamare quando arriva un messaggio piuttosto che passare l'intera classe del controllore.

    
risposta data 09.08.2018 - 16:14
fonte
0

In quel caso particolare (quando devi notificare a qualcuno qualcosa), puoi usare un aggregatore \ messenger.

Uno dei miei preferiti (da C # però) è questo: link

Crei una classe di messaggi, abbonati al servizio, spari il messaggio da un altro servizio - il gioco è fatto. Dietro le quinte, l'aggregatore definisce solo la lista degli abbonati, che dovrebbero essere avvisati per il particolare messaggio.

Sono abbastanza sicuro che Java dovrebbe avere qualcosa di simile.

    
risposta data 09.08.2018 - 16:16
fonte

Leggi altre domande sui tag