Pregunta

Al ejecutar FxCop en mi código, recibo esta advertencia:

  

Microsoft. Mantenimiento:   'FooBar.ctor se combina con 99   diferentes tipos de 9 diferentes   espacios de nombres Reescribe o refactoriza el   método para disminuir su acoplamiento de clase,   o considere mover el método a uno   de los otros tipos es firmemente   junto con. Un acoplamiento de clase arriba   40 indica poca capacidad de mantenimiento, un   clase de acoplamiento entre 40 y 30   indica mantenimiento moderado,   y una clase de acoplamiento por debajo de 30   indica buena mantenibilidad.

Mi clase es una zona de aterrizaje para todos los mensajes del servidor. El servidor puede enviarnos mensajes de diferentes tipos de 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
}

El " HandleSignOut " y " HandleConnectionTest " los métodos tienen poco código en ellos; generalmente pasan el trabajo a una función en otra clase.

¿Cómo puedo mejorar esta clase con un acoplamiento más bajo?

¿Fue útil?

Solución

Haga que las clases que realizan el trabajo se registren para los eventos que les interesan ... an patrón de agente de eventos .

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

Otros consejos

También podría usar algún tipo de marco IoC, como Spring.NET, para inyectar el diccionario. De esta manera, si obtiene un nuevo tipo de mensaje, no tiene que volver a compilar este concentrador central, solo cambie un archivo de configuración.


El ejemplo largamente esperado:

Cree una nueva aplicación de consola, llamada Ejemplo, y agregue esto:

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

Y un archivo 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>

Salida:

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

Como puede ver, MessageBroker no conoce ninguno de los controladores, y los controladores no conocen MessageHandler<>. Toda la asignación se realiza en el archivo app.config, de modo que si necesita manejar un nuevo tipo de evento, puede agregarlo en el archivo de configuración. Esto es especialmente bueno si otros equipos están definiendo tipos de eventos y controladores: simplemente pueden compilar sus cosas en un dll, lo colocan en su implementación y simplemente agregan un mapeo.

El Diccionario tiene valores de tipo objeto en lugar de MessageHandler<EventArgs> porque los controladores reales no se pueden convertir a <=>, así que tuve que hackearlo un poco. Creo que la solución aún está limpia y maneja bien los errores de mapeo. Tenga en cuenta que también necesitará hacer referencia a Spring.Core.dll en este proyecto. Puede encontrar las bibliotecas aquí , y la documentación aquí . El capítulo de inyección de dependencia es relevante para esto . También tenga en cuenta que no hay ninguna razón por la que necesite usar Spring.NET para esto: la idea importante aquí es la inyección de dependencia. De alguna manera, algo tendrá que decirle al intermediario que envíe mensajes de tipo a a x, y usar un contenedor de IoC para la inyección de dependencia es una buena manera de que el intermediario no sepa acerca de x, y viceversa.

Alguna otra pregunta SO relacionada con IoC y DI:

No veo el resto de su código, pero intentaría crear un número mucho menor de clases de argumentos de evento. En su lugar, cree algunos que sean similares entre sí en términos de datos contenidos y / o la forma en que los maneje más adelante y agregue un campo que le dirá qué tipo exacto de evento ocurrió (probablemente debería usar una enumeración).

Idealmente, no solo haría que este constructor sea mucho más legible, sino también la forma en que se manejan los mensajes (mensajes grupales que se manejan de manera similar en un solo controlador de eventos)

Quizás en lugar de tener una clase diferente para cada mensaje, use una bandera que identifique el mensaje.

Eso reduciría drásticamente la cantidad de mensajes que tiene y aumentaría la capacidad de mantenimiento. Supongo que la mayoría de las clases de mensajes tienen una diferencia de cero.

Es difícil elegir una forma adicional de atacar esto porque el resto de la arquitectura es desconocida (para mí).

Si nos fijamos en Windows, por ejemplo, no sabe de manera nativa cómo manejar cada mensaje que se pueda generar. En cambio, los manejadores de mensajes subyacentes registran funciones de devolución de llamada con el hilo principal.

Puede adoptar un enfoque similar. Cada clase de mensaje necesitaría saber cómo manejarse y podría registrarse con la aplicación más grande. Esto debería simplificar enormemente el código y deshacerse del acoplamiento apretado.

Obviamente necesita un mecanismo de envío: dependiendo del evento que reciba, desea ejecutar un código diferente.

Parece que está utilizando el sistema de tipos para identificar los eventos, mientras que en realidad está destinado a soportar el polimorfismo. Como Chris Lively sugiere, también podría (sin abusar del sistema de tipos) usar una enumeración para identificar los mensajes.

O puede adoptar el poder del sistema de tipos y crear un objeto de Registro donde se registra cada tipo de evento (por una instancia estática, un archivo de configuración o lo que sea). Entonces podría usar el patrón de la Cadena de responsabilidad para encontrar el controlador adecuado. O el controlador realiza el manejo por sí mismo, o puede ser una Fábrica, creando un objeto que maneja el evento.

El último método parece un poco poco especificado y con una ingeniería excesiva, pero en el caso de 99 tipos de eventos (ya), me parece apropiado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top