Frage

Beim Laufen FxCop auf meinem Code, erhalte ich diese Warnung:

  

Microsoft.Maintainability:   ‚FooBar.ctor gekoppelt ist mit 99   verschiedene Typen von 9 verschiedenen   Namensräume. Rewrite oder Umgestalten der   Verfahren seiner Klasse Kopplung zu verringern,   oder prüfen, um die Methode zu einem sich bewegenden   die anderen Arten ist es eng   zusammen mit. Eine Klasse Kopplung über   40 zeigt eine schlechte Wartbarkeit, ein   Klasse Kopplung zwischen 40 und 30   zeigt moderate Wartbarkeit,   und eine Klasse Kopplung unter 30   zeigt eine gute Wartbarkeit.

Meine Klasse ist eine Landezone für alle Nachrichten vom Server. Der Server kann uns Nachrichten verschiedenen EventArgs-Typen an:

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

Die "HandleSignOut" und "HandleConnectionTest" Methoden haben wenig Code in ihnen; sie in der Regel die Arbeit an eine Funktion in einer anderen Klasse abgehen.

Wie kann ich diese Klasse besser mit niedriger Kopplung machen?

War es hilfreich?

Lösung

Haben die Klassen, die das Arbeitsregister für Veranstaltungen zu tun sind sie interessieren ... ein Event-Makler Muster.

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

Andere Tipps

Sie können auch eine Art von IoC-Framework, wie Spring.NET, verwenden Sie das Wörterbuch zu injizieren. Auf diese Weise, wenn Sie einen neuen Nachrichtentyp zu erhalten, müssen Sie sie nicht diese zentrale Nabe neu kompilierst - nur eine Konfigurationsdatei ändern.


Das lang erwartete Beispiel:

Erstellen Sie eine neue Konsolenanwendung, mit dem Namen Beispiel, und fügen Sie diese:

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 {}
}

Und eine app.config-Datei:

<?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>

Ausgabe:

Type 1, Example.Type1EventArgs
Type 2, Example.Type2EventArgs
No handler defined for event type: EventArgs

Wie Sie sehen können, MessageBroker weiß nicht, über eine der Handler und die Handler nicht wissen, über MessageBroker. Alle von der Abbildung ist in der app.config-Datei durchgeführt, so dass, wenn Sie einen neuen Ereignistyp behandeln müssen, können Sie es in der Konfigurationsdatei hinzufügen. Dies ist besonders schön, wenn andere Teams Ereignistypen und Handler definieren - sie ihre Sachen in einem dll nur kompilieren können, fallen Sie es in Ihre Bereitstellung und einfach eine Zuordnung hinzufügen

.

Das Wörterbuch hat Werte vom Typ Objekt statt MessageHandler<>, weil die tatsächlichen Handler nicht gegossen werden können, um MessageHandler<EventArgs>, also musste ich das ein bisschen hacken um. Ich denke, die Lösung noch sauber ist, und es Griffe gut Zuordnungsfehler. Beachten Sie, dass Sie auch Spring.Core.dll in diesem Projekt verweisen müssen. Sie können die Bibliotheken finden hier und die Dokumentation hier . Die Dependency Injection Kapitel dies relevant . Beachten Sie auch, gibt es keinen Grund, warum Sie benötigen Spring.NET dafür nutzen - die wichtige Idee hier ist, Dependency Injection. Irgendwie ist etwas zu müssen geht den Broker sagen Nachrichten vom Typ a bis x zu senden, und unter Verwendung eines IoC-Container für Dependency Injection ist ein guter Weg, den Makler nicht über x wissen zu haben, und umgekehrt.

Einige andere Frage SO im Zusammenhang mit IoC und DI:

Ich sehe nicht den Rest des Codes, aber ich würde versuchen, eine viel kleinere Anzahl von Event-Klassen arg erstellen. Stattdessen erstellen Sie ein paar, die in Bezug auf die Daten einander ähnlich sind enthalten und / oder die Art und Weise Sie sie später behandeln und ein Feld hinzufügen, die Ihnen sagen, was genaue Art der Veranstaltung auftrat (wahrscheinlich sollten Sie eine ENUM verwenden).

Im Idealfall würden Sie nicht nur diesen Konstruktor machen viel besser lesbar, sondern auch die Art und Weise der Nachrichten behandelt werden (Gruppennachrichten, die in einem einzigen Event-Handler in ähnlicher Weise behandelt werden)

Vielleicht stattdessen eine andere Klasse für jede Nachricht zu haben, verwenden Sie eine Flagge, die die Nachricht identifiziert.

Das würde die Anzahl der Nachrichten drastisch reduzieren Sie haben und Wartbarkeit erhöhen. Meine Vermutung ist, dass die meisten der Nachrichtenklassen haben etwa Null Unterschied.

Es ist schwer, eine zusätzliche Möglichkeit, anzugreifen, weil der Rest der Architektur unbekannt zu holen (für mich).

Wenn Sie auf Windows-schauen, zum Beispiel, ist es nicht nativ wissen, wie jede Nachricht verarbeiten, die über geworfen werden könnten. Stattdessen registrieren, um der zugrunde liegende Nachrichten-Handler Rückruffunktionen mit dem Haupt-Thread.

Sie können einen ähnlichen Ansatz. Jede Nachricht Klasse würde müssen wissen, wie sie zu handhaben und mich mit der größeren Anwendung registrieren konnte. Dies sollte stark den Code vereinfachen und die engen Kopplung loszuwerden.

Offensichtlich sind Sie in der Notwendigkeit einer Dispatching Mechanismus: Je nach Fall, dass Sie erhalten, Sie anderen Code ausführen möchten

.

Sie scheinen das Typsystem zu verwenden, die Ereignisse zu identifizieren, während es eigentlich gemeint ist Polymorphismus zu unterstützen. Wie Chris Lively schlägt vor, Sie könnten genauso gut (ohne den Typen System zu missbrauchen) verwenden, um eine aufzuzählen, um die Nachrichten zu identifizieren.

Oder können Sie die Leistung des Typs Systems umfassen, und ein Registry-Objekt erstellen, in dem jede Art von Veranstaltung (durch eine statische Instanz, eine Config-Datei oder was auch immer) registriert ist. Dann könnten Sie die Zuständigkeitskette-Muster verwenden, um die richtigen Handler zu finden. Entweder der Handler funktioniert die Handhabung selbst, oder es kann ein Werk sein, ein Objekt zu schaffen, die das Ereignis behandelt.

Die letztere Methode sieht ein bisschen underspecified und overengineered, aber im Fall von 99 Ereignistypen (bereits), scheint es angemessen zu mir.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top