Domanda

Eseguendo FxCop sul mio codice, ricevo questo avviso:

  

Microsoft.Maintainability:   'FooBar.ctor è accoppiato con 99   tipi diversi da 9 diversi   spazi dei nomi. Riscrivi o rifatti il   metodo per ridurre il suo accoppiamento di classe,   o considerare di spostare il metodo su uno   degli altri tipi è strettamente   accoppiato con. Un accoppiamento di classe sopra   40 indica scarsa manutenibilità, a   accoppiamento di classe tra 40 e 30   indica moderata manutenibilità,   e un accoppiamento di classe inferiore a 30   indica una buona manutenibilità.

La mia classe è una zona di atterraggio per tutti i messaggi dal server. Il server può inviarci messaggi di diversi tipi di 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
}

Il " HandleSignOut " e " HandleConnectionTest " i metodi hanno poco codice in essi; di solito passano il lavoro a una funzione in un'altra classe.

Come posso migliorare questa classe con un accoppiamento inferiore?

È stato utile?

Soluzione

Avere le classi che eseguono il registro di lavoro per gli eventi a cui sono interessati ... an modello di evento .

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

Altri suggerimenti

Puoi anche usare una sorta di framework IoC, come Spring.NET, per iniettare il dizionario. In questo modo, se si ottiene un nuovo tipo di messaggio, non è necessario ricompilare questo hub centrale: basta modificare un file di configurazione.


L'esempio tanto atteso:

Crea una nuova app console, denominata Esempio, e aggiungi questo:

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

E un file 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>

Output:

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

Come puoi vedere, MessageBroker non conosce nessuno dei gestori e i gestori non conoscono MessageHandler<>. Tutta la mappatura viene eseguita nel file app.config, in modo che se è necessario gestire un nuovo tipo di evento, è possibile aggiungerlo nel file di configurazione. Ciò è particolarmente utile se altri team stanno definendo tipi di eventi e gestori: possono semplicemente compilare le loro cose in una dll, lasciarle cadere nella tua distribuzione e semplicemente aggiungere una mappatura.

Il dizionario ha valori di tipo oggetto invece di MessageHandler<EventArgs> perché i gestori effettivi non possono essere sottoposti a cast su <=>, quindi ho dovuto scavare un po '. Penso che la soluzione sia ancora pulita e gestisca bene gli errori di mappatura. Nota che dovrai anche fare riferimento a Spring.Core.dll in questo progetto. Puoi trovare le librerie qui e la documentazione qui . Il capitolo di iniezione di dipendenza è pertinente per questo . Inoltre, non vi è alcun motivo per cui è necessario utilizzare Spring.NET per questo - l'idea importante qui è l'iniezione di dipendenza. In qualche modo, qualcosa dovrà dire al broker di inviare messaggi di tipo da ax, e l'uso di un contenitore IoC per l'iniezione delle dipendenze è un buon modo per far sì che il broker non conosca x, e viceversa.

Qualche altra domanda SO relativa a IoC e DI:

Non vedo il resto del codice, ma proverei a creare un numero molto più piccolo di classi arg Event. Invece creane alcuni simili tra loro in termini di dati contenuti e / o nel modo in cui li gestirai in seguito e aggiungi un campo che ti dirà quale tipo esatto di evento si è verificato (probabilmente dovresti usare un enum).

Idealmente non solo renderesti questo costruttore molto più leggibile, ma anche il modo in cui i messaggi vengono gestiti (raggruppa i messaggi che sono gestiti in modo simile in un singolo gestore di eventi)

Forse invece di avere una classe diversa per ogni messaggio, utilizzare un flag che identifichi il messaggio.

Ciò ridurrebbe drasticamente il numero di messaggi che hai e aumenterebbe la manutenibilità. La mia ipotesi è che la maggior parte delle classi di messaggi ha circa la differenza zero.

È difficile scegliere un altro modo per attaccare questo perché il resto dell'architettura è sconosciuto (per me).

Se guardi Windows, ad esempio, non sa nativamente come gestire ogni messaggio che potrebbe essere lanciato. Al contrario, i gestori di messaggi sottostanti registrano le funzioni di callback con il thread principale.

Potresti adottare un approccio simile. Ogni classe di messaggi dovrebbe sapere come gestire se stessa e potrebbe registrarsi con l'applicazione più grande. Ciò dovrebbe semplificare notevolmente il codice ed eliminare l'accoppiamento stretto.

Ovviamente hai bisogno di un meccanismo di dispacciamento: a seconda dell'evento che ricevi, vuoi eseguire codice diverso.

Sembra che tu stia usando il sistema dei tipi per identificare gli eventi, mentre in realtà è pensato per supportare il polimorfismo. Come suggerisce Chris Lively, potresti anche (senza abusare del sistema dei tipi) utilizzare un elenco per identificare i messaggi.

Oppure puoi abbracciare la potenza del sistema di tipi e creare un oggetto Registro in cui ogni tipo di evento è registrato (da un'istanza statica, un file di configurazione o qualsiasi altra cosa). Quindi è possibile utilizzare il modello Catena di responsabilità per trovare il gestore corretto. Il gestore si occupa da solo o può essere una Factory, creando un oggetto che gestisce l'evento.

Quest'ultimo metodo sembra un po 'meno specificato e troppo ingegnerizzato, ma nel caso di 99 tipi di eventi (già), mi sembra appropriato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top