web-dev-qa-db-ja.com

2つのクラス間の循環依存関係を解決する

システム内の2つのコンポーネント間の循環依存関係を解決しようとしています。 Messengerコンポーネントは、Webソケットでのメッセージの送受信を担当します。 Controllerコンポーネントは接続されたセッションについて知っているため、Messengerコンポーネントは、接続されたクライアントにメッセージを送信できるようにするためにMessengerコンポーネントを使用する必要があります。

MessengerコンポーネントはControllerコンポーネントに依存しており、着信メッセージをコントローラーに通知できます。

WebsocketHandlerは、この場合は制御できないフレームワークインターフェイスであり、接続されたクライアントに基づいて、実装で適切なメソッドを呼び出します。

これを解決するために、どのようなアーキテクチャの変更を加えることができますか?

UML Diagram

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(...);
    }
}
4
Robert Hunt

これに対する簡単な解決策は、ControllerMessengerへの依存関係を必要とせず、sendへの依存関係を必要とすることを認識することです。したがって、その依存関係を提供します。

_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(...);
    }
}
_

現在、ControllerSenderインターフェースに依存しているだけなので、その循環依存は削除されています。

[〜#〜]更新[〜#〜]

OPが指摘するように、上記はControllerMessengerの間の直接循環依存関係を削除します。ただし、コンストラクター注入が使用される場合、依存関係はオブジェクトの作成時にまだ存在します。SenderControllerのニーズを満たすために、最初にSenderを作成する必要があります。 Controllercontroller.messageReceived(payload)を呼び出す必要があるため、最初にMessengerを作成する必要があります。

プロパティインジェクションのサポートなど、これを回避するにはさまざまな方法があります。しかし、個人的に気に入っているのは、ファーストクラスの市民として関数をサポートする言語の場合、関数を介して2つのオブジェクトを完全に分離することです。

このようなソリューションは、C#で表現されます(その言語に精通しているため)。

_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);
}
_

そして、実行時に2つのオブジェクトを結合するコードは次のようになります。

_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もそのようなアプローチをサポートしています。これがSpringのようなIoCフレームワークでうまく機能するかどうかはわかりません。

6
David Arno

Messengerコンポーネントは、コントローラーコンポーネントに依存して、コントローラーに着信メッセージを通知できるようにします。

OMG Javaにはイベントがありません!!!

ただし、いくつかの選択肢があります。例えば

https://docs.Oracle.com/javase/6/docs/api/Java/util/Observable.html

ここで、メッセンジャーにObservableを実装させ、コントローラーにaddObserverを呼び出させることができます。メッセンジャーは、コントローラーを参照することなく、notifyObservers()関数を使用してコントローラーに着信メッセージを通知できますクラス。

テクニックにはいくつかのバリエーションがあります。

https://docs.Oracle.com/javase/tutorial/uiswing/events/index.html

考え方としては、コントローラクラス全体ではなく、メッセージが届いたときに呼び出す関数をメッセンジャーに渡します。

1
Ewan

その特定の場合(誰かに何かについて通知する必要がある場合)、アグリゲーター\メッセンジャーを使用できます。

(C#からの)私のお気に入りの1つはこれです: https://github.com/NimaAra/Easy.MessageHub

メッセージクラスを作成し、サービスをサブスクライブし、別のサービスからメッセージを起動します-それだけです。バックグラウンドで、アグリゲーターは、特定のメッセージについて通知を受ける必要があるサブスクライバーのリストを定義するだけです。

Javaも同様のはずです。

0