0

最近、誰かがクラス1の形式での循環参照がクラス2の呼び出しをクラス1と見なす方法を教えてくれましたが、私はそれについて頭を悩ませることができません。つまり、それらのクラスが2つの異なるプロジェクトにある場合、問題は完全に理解できますが、同じプロジェクトにある場合、それは悪いことでしょうか?そして、場合によっては...それをどの程度正確に防ぐのですか?

例:ある種のサーバーがあります。クライアントはそれに接続し、ソケットから派生した、またはソケットを保持するクライアントは、ネットワーク関連のもの、およびアカウントIDなどの情報を処理します。このクライアントは、何か新しいことがあると、パケットハンドラーを呼び出します。パケットハンドラはクライアントからの情報を必要とし、情報を送り返す必要があります。クライアントをパケットハンドラーに渡すので、クライアントは送信関数などを呼び出すことができます。

これを心配した人は、これは言及された悪い習慣だと思い、クライアントクラス内ですべてのパケット処理を維持するサーバー、特に大きなサーバーはめったに見たことがありませんでしたが、これをまったく行わないようにしました。さらに、ハンドラーよりも先に進んで、より多くのクラスを呼び出すことができます。それをすべてクライアント内に保持するのは面倒です。それで...これは本当に悪い習慣ですか?

もしそうなら、どのようにそれを回避しますか?これを行うには、Clientの関数を再度呼び出すことなく、受け継ぐことができる、多かれ少なかれ複雑なオブジェクトのコレクションが必要になります...

私が言ったように、私はこの「問題」に頭を悩ませることはできません。誰かが私を助けることができますか?

4

4 に答える 4

5

循環参照について話すとき、彼らは通常、プロジェクト/dll参照について話します。これらは参照の解決に関して問題があり、VisualStudioでは循環参照を追加できません。

しかし、あなたはプロジェクトの構造ではなくアーキテクチャを参照しているので、ここではもう少し複雑です。クラスが相互に呼び出すことには本質的に問題はありません。実際、これは.NETのコールバックやイベントなどの機能の設計に暗黙的に含まれています。イベントに登録すると、後でイベントハンドラーを使用してコールバックするクラスを効果的に呼び出しています。

ただし、この形式の循環呼び出しは比較的分離されています。サーバーにはクライアントへのEXPLICIT参照はありませんが、呼び出されるサブスクライブされたクライアントのリストのみがあります。それを行わずに、たとえばパケットハンドラーがクライアントへの明示的な参照を保持している場合、これら2つのクラスは緊密に結合されます。パケットハンドラーはクライアントの特定の実装に依存し、その逆も同様です。

なぜこれが悪いのですか?私の意見では、これはプログラミングの最も基本的な概念の1つである関心の分離の原則に違反しています。クライアントはクライアント操作の処理方法を知っている必要があり、パケットハンドラーはパケット操作を処理する必要があり、どちらももう一方がどのように機能するかを知らない必要があり、明確に定義された特定のインターフェイスを介してのみ通信します。

OPに基づいており、循環参照がある非常に簡略化された架空の状況を考えてみましょう。クライアントはパケットハンドラーのSend()メソッドを呼び出します。パケットハンドラーは接続を開始し、ユーザー名/パスワードが必要であることを検出します。クライアントのメソッドを呼び出して取得し、サーバーに送信して応答を取得し、クライアントにコールバックして返します。

この場合、パケットハンドラーはクライアントの実装に関連付けられています。クライアントには、GetCredentials()メソッドとそれを呼び出すためのMessageReceivedメソッドが必要です。ここで、より分離されたシナリオを想像してください。

クライアントは最初にハンドラーのResponseReceivedイベントにサインアップします。ここで、クライアントはパケットハンドラのSend()メソッドを呼び出します。パケットハンドラは認証が必要なため、失敗します。例外をスローするか、「接続できません」というエラーコードを返します。クライアントはこの応答を受け取り、今度はSend(username、password)メソッドを使用して再度呼び出します。成功し、応答を取得し、ResponseReceivedイベントを発生させて、サブスクライブしている人に応答を送信します。

これにより、パケットハンドラーを他のクライアントから他のコンテキストで再利用できます。これにより、他のコンポーネントへの影響を少なくして、クライアントまたはハンドラーに対して内部的に変更を加えることができます。これにより、コードがよりシンプルになり、保守が容易になります。そして、それは良いことです。:)

于 2012-04-18T07:28:38.123 に答える
3

循環クラス参照がまったく悪くない状況は確かにあります。インスタンス化の問題(鶏が先か卵が先か)は常にあります。オブジェクト間の双方向通信はすでに存在することを常に忘れないでください。新しいメッセージがあるときに戻る関数を呼び出すことで、クライアントに次のメッセージを待たせることができます。

しかし、それはあなたが立ち止まって考えるべきポイントです。特に次の状況では(もっとありますが、これらは私の頭のてっぺんにあります):

  • クラスに通知しているときに、何らかのアクションが発生しました->イベントにします。
  • 1対1の関係の場合->タイプを1つにマージする必要があります。
  • あなたのクラスが他の機能を拡張するとき->参照を渡す代わりにそれから継承します。

あなたの状況では、クライアント<->パケットハンドラー。私はおそらくそこに循環参照を保持しないでしょう。私はおそらく、両方のクラスのコントローラーを作成し、それらの間の情報のプロキシとして機能します。また、クライアントオブジェクトにアカウントIDを保存しません。それは、私が固守したい単一責任の原則に少し反します。私はおそらく次のようなものになってしまうでしょう:

  • クライアント->ソケット(ネットワーク関連のものを担当)
  • ClientHandler(フローの制御を担当)
    • クライアント
    • PacketHandler
    • 追加情報
  • PacketHandler
于 2012-04-18T07:29:19.103 に答える
2

同じパッケージ内のクラスは、非常にまとまりがあると見なす必要があり(パッケージ構造が正しいと仮定します!)、より緊密な結合を持つことができます。パッケージ内では、循環関係(必要な場合)は問題ありませんが、特定のインターフェースに基づく設計の恩恵を受けます(以前のポスターで指摘されています)。

パッケージの境界を越えて、凝集度は当然低くなり、結合は可能な限り低く保つ必要があります。循環関係は絶対にありません。

于 2012-04-18T09:48:27.190 に答える
0

AがBに呼び出す機能と、BがAに呼び出す機能を抽出し、それらをクラスライブラリCにカプセル化して、他のライブラリが問題なく呼び出すことができるようにすることができます。明らかに、CはAにもBにも参照してはなりません。

于 2012-04-18T07:03:27.690 に答える