システム内の2つのコンポーネント間の循環依存関係を解決しようとしています。 Messenger
コンポーネントは、Webソケットでのメッセージの送受信を担当します。 Controller
コンポーネントは接続されたセッションについて知っているため、Messenger
コンポーネントは、接続されたクライアントにメッセージを送信できるようにするためにMessenger
コンポーネントを使用する必要があります。
Messenger
コンポーネントはController
コンポーネントに依存しており、着信メッセージをコントローラーに通知できます。
WebsocketHandler
は、この場合は制御できないフレームワークインターフェイスであり、接続されたクライアントに基づいて、実装で適切なメソッドを呼び出します。
これを解決するために、どのようなアーキテクチャの変更を加えることができますか?
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(...);
}
}
これに対する簡単な解決策は、Controller
がMessenger
への依存関係を必要とせず、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(...);
}
}
_
現在、Controller
はSender
インターフェースに依存しているだけなので、その循環依存は削除されています。
[〜#〜]更新[〜#〜]
OPが指摘するように、上記はController
とMessenger
の間の直接循環依存関係を削除します。ただし、コンストラクター注入が使用される場合、依存関係はオブジェクトの作成時にまだ存在します。Sender
のController
のニーズを満たすために、最初にSender
を作成する必要があります。 Controller
はcontroller.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フレームワークでうまく機能するかどうかはわかりません。
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
考え方としては、コントローラクラス全体ではなく、メッセージが届いたときに呼び出す関数をメッセンジャーに渡します。
その特定の場合(誰かに何かについて通知する必要がある場合)、アグリゲーター\メッセンジャーを使用できます。
(C#からの)私のお気に入りの1つはこれです: https://github.com/NimaAra/Easy.MessageHub
メッセージクラスを作成し、サービスをサブスクライブし、別のサービスからメッセージを起動します-それだけです。バックグラウンドで、アグリゲーターは、特定のメッセージについて通知を受ける必要があるサブスクライバーのリストを定義するだけです。
Javaも同様のはずです。