8

コードで FxCop を実行すると、次の警告が表示されます。

Microsoft.Maintainability : 'FooBar.ctor は、9 つ​​の異なる名前空間からの 99 の異なる型と結合されています。メソッドを書き直すかリファクタリングして、クラスの結合を減らすか、メソッドを他の型のいずれかに移動することを検討してください。40 を超えるクラス結合は保守性が低いことを示し、クラス結合が 40 から 30 の間の場合は保守性が中程度であることを示し、クラス結合が 30 未満である場合は保守性が良好であることを示します。

私のクラスは、サーバーからのすべてのメッセージのランディング ゾーンです。サーバーは、さまざまな EventArgs タイプのメッセージを送信できます。

public FooBar()
{
    var messageHandlers = new Dictionary<Type, Action<EventArgs>>();
    messageHandlers.Add(typeof(YouHaveBeenLoggedOutEventArgs), HandleSignOut);
    messageHandlers.Add(typeof(TestConnectionEventArgs), HandleConnectionTest);
    // ... etc for 90 other types
}

「HandleSignOut」および「HandleConnectionTest」メソッドにはほとんどコードが含まれていません。通常、別のクラスの関数に作業を渡します。

低結合でこのクラスを改善するにはどうすればよいですか?

4

5 に答える 5

15

作業を行うクラスに、関心のあるイベントを登録させます...イベント ブローカーパターン。

class EventBroker {
   private Dictionary<Type, Action<EventArgs>> messageHandlers;

   void Register<T>(Action<EventArgs> subscriber) where T:EventArgs {
      // may have to combine delegates if more than 1 listener
      messageHandlers[typeof(T)] = subscriber; 
   }

   void Send<T>(T e) where T:EventArgs {
      var d = messageHandlers[typeof(T)];
      if (d != null) {
         d(e);
      }
   }
}
于 2008-10-09T21:02:31.723 に答える
5

また、Spring.NETなどのある種のIoCフレームワークを使用して、辞書を挿入することもできます。このように、新しいメッセージタイプを取得した場合、この中央ハブを再コンパイルする必要はありません。構成ファイルを変更するだけです。


待望の例:

Exampleという名前の新しいコンソールアプリを作成し、次を追加します。

using System;
using System.Collections.Generic;
using Spring.Context.Support;

namespace Example
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            MessageBroker broker = (MessageBroker) ContextRegistry.GetContext()["messageBroker"];
            broker.Dispatch(null, new Type1EventArgs());
            broker.Dispatch(null, new Type2EventArgs());
            broker.Dispatch(null, new EventArgs());
        }
    }

    public class MessageBroker
    {
        private Dictionary<Type, object> handlers;

        public Dictionary<Type, object> Handlers
        {
            get { return handlers; }
            set { handlers = value; }
        }

        public void Dispatch<T>(object sender, T e) where T : EventArgs
        {
            object entry;
            if (Handlers.TryGetValue(e.GetType(), out entry))
            {
                MessageHandler<T> handler = entry as MessageHandler<T>;
                if (handler != null)
                {
                    handler.HandleMessage(sender, e);
                }
                else
                {
                    //I'd log an error here
                    Console.WriteLine("The handler defined for event type '" + e.GetType().Name + "' doesn't implement the correct interface!");
                }
            }
            else
            {
                //I'd log a warning here
                Console.WriteLine("No handler defined for event type: " + e.GetType().Name);
            }
        }
    }

    public interface MessageHandler<T> where T : EventArgs
    {
        void HandleMessage(object sender, T message);
    }

    public class Type1MessageHandler : MessageHandler<Type1EventArgs>
    {
        public void HandleMessage(object sender, Type1EventArgs args)
        {
            Console.WriteLine("Type 1, " + args.ToString());
        }
    }

    public class Type2MessageHandler : MessageHandler<Type2EventArgs>
    {
        public void HandleMessage(object sender, Type2EventArgs args)
        {
            Console.WriteLine("Type 2, " + args.ToString());
        }
    }

    public class Type1EventArgs : EventArgs {}

    public class Type2EventArgs : EventArgs {}
}

そしてapp.configファイル:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
    </sectionGroup>
  </configSections>

  <spring>
    <context>
      <resource uri="config://spring/objects"/>
    </context>
    <objects xmlns="http://www.springframework.net">

      <object id="messageBroker" type="Example.MessageBroker, Example">
        <property name="handlers">
          <dictionary key-type="System.Type" value-type="object">
            <entry key="Example.Type1EventArgs, Example" value-ref="type1Handler"/>
            <entry key="Example.Type2EventArgs, Example" value-ref="type2Handler"/>
          </dictionary>
        </property>
      </object>
      <object id="type1Handler" type="Example.Type1MessageHandler, Example"/>
      <object id="type2Handler" type="Example.Type2MessageHandler, Example"/>
    </objects>
  </spring>
</configuration>

出力:

タイプ1、Example.Type1EventArgs
タイプ2、Example.Type2EventArgs
イベントタイプにハンドラーが定義されていません:EventArgs

ご覧のとおりMessageBroker、ハンドラーについては知りません。また、ハンドラーはについて知りませんMessageBroker。すべてのマッピングはapp.configファイルで行われるため、新しいイベントタイプを処理する必要がある場合は、それを構成ファイルに追加できます。これは、他のチームがイベントタイプとハンドラーを定義している場合に特に便利です。それらは、dllにコンパイルするだけで、デプロイメントにドロップして、マッピングを追加するだけです。

MessageHandler<>実際のハンドラーをキャストできないため、ディクショナリにはオブジェクト型の値がありMessageHandler<EventArgs>ます。そのため、少しハックする必要がありました。ソリューションはまだクリーンであり、マッピングエラーを適切に処理すると思います。このプロジェクトではSpring.Core.dllも参照する必要があることに注意してください。ライブラリはここにあり、ドキュメントはここにあります。依存性注入の章はこれに関連しています。また、これにSpring.NETを使用する必要がある理由はありません。ここで重要なのは依存性注入です。どういうわけか、タイプaからxのメッセージを送信するようにブローカーに指示する必要があります。依存性注入にIoCコンテナーを使用することは、ブローカーにxについて知らないようにするための良い方法です。

IoCとDIに関連するその他のSOの質問:

于 2008-10-09T21:00:33.770 に答える
0

おそらく、メッセージごとに異なるクラスを使用する代わりに、メッセージを識別するフラグを使用してください。

これにより、メッセージの数が大幅に減り、保守性が向上します。私の推測では、ほとんどのメッセージクラスの違いはほぼゼロです。

アーキテクチャの残りの部分は(私には)不明であるため、これを攻撃する追加の方法を選択するのは困難です。

たとえば、Windowsを見ると、スローされる可能性のある各メッセージの処理方法がネイティブにわかりません。代わりに、基になるメッセージハンドラーは、コールバック関数をメインスレッドに登録します。

同様のアプローチを取るかもしれません。各メッセージクラスは、それ自体を処理する方法を知っている必要があり、より大きなアプリケーションにそれ自体を登録できます。これにより、コードが大幅に簡素化され、密結合が解消されます。

于 2008-10-09T20:51:30.483 に答える
0

残りのコードは表示されませんが、はるかに少ない数のEventargクラスを作成してみます。代わりに、含まれるデータや後でそれらを処理する方法の点で互いに類似しているものをいくつか作成し、発生したイベントの正確なタイプを示すフィールドを追加します(おそらく列挙型を使用する必要があります)。

理想的には、このコンストラクターをはるかに読みやすくするだけでなく、メッセージの処理方法(単一のイベントハンドラーで同様の方法で処理されるメッセージをグループ化する)も作成します。

于 2008-10-09T21:01:21.367 に答える
0

明らかに、ディスパッチ メカニズムが必要です。受信したイベントに応じて、異なるコードを実行する必要があります。

型システムを使用してイベントを識別しているようですが、実際にはポリモーフィズムをサポートするためのものです。Chris Lively が示唆しているように、(型システムを悪用することなく) 列挙型を使用してメッセージを識別することもできます。

または、型システムの力を利用して、あらゆる種類のイベントが (静的インスタンス、構成ファイルなどによって) 登録される Registry オブジェクトを作成することもできます。次に、Chain of Responsibility パターンを使用して、適切なハンドラーを見つけることができます。ハンドラー自体が処理を行うか、イベントを処理するオブジェクトを作成する Factory である可能性があります。

後者の方法は、少し仕様が不十分で過剰に設計されているように見えますが、99 個のイベント タイプの場合 (既に)、適切なように思えます。

于 2008-10-09T21:14:39.063 に答える