Question

En exécutant FxCop sur mon code, je reçois cet avertissement:

  

Microsoft.Maintainability:   'FooBar.ctor est couplé avec 99   différents types de 9 différents   espaces de noms. Réécrire ou refactoriser le   méthode pour diminuer son couplage de classe,   ou envisagez de déplacer la méthode vers un   des autres types, il est étroitement   associé à. Un couplage de classe ci-dessus   40 indique une mauvaise maintenabilité, un   couplage de classe entre 40 et 30   indique une maintenabilité modérée,   et un couplage de classe inférieur à 30   indique une bonne maintenabilité.

Ma classe est une zone de destination pour tous les messages du serveur. Le serveur peut nous envoyer des messages de différents types 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
}

Le " HandleSignOut " et " HandleConnectionTest " les méthodes ont peu de code en elles; ils confient généralement le travail à une fonction d'une autre classe.

Comment puis-je améliorer cette classe avec un couplage faible?

Était-ce utile?

La solution

Demandez aux classes qui effectuent ce travail de s'inscrire aux événements qui les intéressent ... un modèle de courtier d'événements .

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

Autres conseils

Vous pouvez également utiliser une sorte de framework IoC, tel que Spring.NET, pour injecter le dictionnaire. Ainsi, si vous obtenez un nouveau type de message, vous n'avez pas besoin de recompiler ce hub central - il vous suffit de modifier un fichier de configuration.


L'exemple tant attendu:

Créez une nouvelle application de console, nommée Example, et ajoutez ceci:

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

Et un fichier 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>

Sortie:

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

Comme vous pouvez le constater, MessageBroker ne connaît aucun des gestionnaires, et les gestionnaires ne connaissent pas MessageHandler<>. Tout le mappage est effectué dans le fichier app.config. Par conséquent, si vous devez gérer un nouveau type d'événement, vous pouvez l'ajouter dans le fichier de configuration. C’est particulièrement intéressant si d’autres équipes définissent des types d’événements et des gestionnaires. Elles peuvent simplement compiler leurs informations dans une DLL, les déposer dans votre déploiement et ajouter simplement un mappage.

Le dictionnaire a les valeurs de type objet au lieu de MessageHandler<EventArgs> parce que les gestionnaires actuels ne peuvent pas être convertis en <=>, j'ai donc dû en parler un peu. Je pense que la solution est toujours propre et qu'elle gère bien les erreurs de mappage. Notez que vous devrez également faire référence à Spring.Core.dll dans ce projet. Vous pouvez trouver les bibliothèques ici , ainsi que de la documentation ici . Le chapitre d'injection de dépendance est pertinent pour cette . Notez également qu'il n'y a aucune raison d'utiliser Spring.NET pour cela. L'idée importante ici est l'injection de dépendance. Quelque part, quelque chose va devoir dire au courtier d’envoyer des messages de type a à x, et utiliser un conteneur IoC pour l’injection de dépendances est un bon moyen d’empêcher le courtier de ne pas connaître x, et vice-versa.

Une autre question SO liée à IoC et DI:

Je ne vois pas le reste de votre code, mais je voudrais créer un nombre beaucoup plus réduit de classes d'argument d'événements. Créez plutôt quelques-uns qui sont similaires en termes de données contenues et / ou de la façon dont vous les gérez plus tard et ajoutez un champ qui vous indiquera le type exact d'événement survenu (vous devriez probablement utiliser une énumération).

Idéalement, vous rendrez non seulement ce constructeur beaucoup plus lisible, mais également la manière dont les messages sont traités (les messages de groupe traités de manière similaire dans un seul gestionnaire d'événements)

Peut-être qu'au lieu d'avoir une classe différente pour chaque message, utilisez un drapeau identifiant le message.

Cela réduirait considérablement le nombre de messages que vous avez et augmenterait la facilité de maintenance. À mon avis, la plupart des classes de messages ont une différence de zéro.

Il est difficile de choisir un moyen supplémentaire d’attaquer ce problème, car le reste de l’architecture est inconnue (pour moi).

Si vous regardez Windows, par exemple, il ne sait pas comment gérer chaque message qui pourrait être envoyé. Au lieu de cela, les gestionnaires de messages sous-jacents enregistrent les fonctions de rappel avec le thread principal.

Vous pouvez adopter une approche similaire. Chaque classe de message doit savoir comment se gérer et peut s’enregistrer avec la plus grande application. Cela devrait grandement simplifier le code et se débarrasser du couplage étroit.

Évidemment, vous avez besoin d’un mécanisme de répartition: en fonction de l’événement que vous recevez, vous souhaitez exécuter un code différent.

Vous semblez utiliser le système de types pour identifier les événements, alors qu'il est censé prendre en charge le polymorphisme. Comme le suggère Chris Lively, vous pouvez tout aussi bien (sans abuser du système de typage) utiliser une énumération pour identifier les messages.

Vous pouvez également profiter de la puissance du système de types et créer un objet de registre dans lequel chaque type d’événement est enregistré (par une instance statique, un fichier de configuration ou autre). Vous pouvez ensuite utiliser le modèle Chaîne de responsabilité pour trouver le bon gestionnaire. S’il s’agit d’un gestionnaire qui gère lui-même l’événement, le gestionnaire le gère lui-même.

Cette dernière méthode semble un peu sous-spécifiée et sur-conçue, mais dans le cas de 99 types d’événements (déjà), elle me semble appropriée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top