Pergunta

Running FxCop no meu código, eu recebo este aviso:

Microsoft.Maintainability: 'FooBar.ctor está acoplado com 99 diferentes tipos de 9 diferente namespaces. Reescrever ou refatorar o método para diminuir o seu acoplamento classe, ou considerar a mudança do método a um dos outros tipos é firmemente acompanhado de. Uma classe acima de acoplamento 40 indica má manutenção, uma acoplamento de classes entre 40 e 30 indica manutenção moderada, e uma classe de acoplamento inferior a 30 indica uma boa manutenção.

A minha turma é uma zona de aterragem para todas as mensagens a partir do servidor. O servidor pode enviar mensagens de diferentes tipos 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
}

O "HandleSignOut" e "HandleConnectionTest" métodos têm pouco código neles; eles costumam passar o trabalho fora de uma função em outra classe.

Como posso fazer esta classe melhor com acoplamento mais baixo?

Foi útil?

Solução

Tenha as classes que fazem o registo de trabalho para eventos que estão interessados ??em ... um corretor evento padrão.

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

Outras dicas

Você também pode usar algum tipo de framework IoC, como Spring.NET, para injetar o dicionário. Dessa forma, se você receber um novo tipo de mensagem, você não tem que recompilar este hub central - apenas mudar um arquivo de configuração.


O exemplo aguardada:

Criar um novo aplicativo console, chamado Example, e adicionar o seguinte:

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 um arquivo 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

Como você pode ver, MessageBroker não sabe sobre qualquer um dos manipuladores, e os manipuladores não sei sobre MessageBroker. Todo o mapeamento é feito no arquivo app.config, de modo que se você precisa lidar com um novo tipo de evento, você pode adicioná-lo no arquivo de configuração. Isto é especialmente bom se outras equipes estão definindo os tipos de eventos e manipuladores - eles só podem compilar as suas coisas em uma DLL, você soltá-lo em sua implantação e simplesmente adicionar um mapeamento

.

O dicionário tem valores de tipo de objeto em vez de MessageHandler<> porque os manipuladores de reais não pode ser convertido para MessageHandler<EventArgs>, então eu tive que cortar em torno de que um pouco. Acho que a solução ainda é limpo, e ele lida com erros de mapeamento bem. Note que você também vai precisar fazer referência Spring.Core.dll neste projeto. Você pode encontrar a bibliotecas aqui ea documentação aqui . O href="http://springframework.net/docs/1.1.2/reference/html/objects.html#objects-dependencies" rel="nofollow noreferrer"> capítulo injeção é relevante para esta . Também nota, não há nenhuma razão que você precisa para usar Spring.NET para isso - a idéia importante aqui é a injeção de dependência. De alguma forma, alguma coisa está indo para necessidade de dizer o corretor para enviar mensagens do tipo A para x, e usando um contêiner IoC para injeção de dependência é uma boa maneira de ter o corretor não sabe sobre x, e vice-versa.

Alguns outra questão SO relacionadas com IoC e DI:

Não vejo o resto do seu código, mas gostaria de tentar criar um número muito menor de classes de eventos arg. Em vez disso criar alguns que são semelhantes entre si em termos de dados contidos e / ou a forma como você lida com eles mais tarde e adicionar um campo que vai dizer o que exatamente os tipos de evento ocorreu (provavelmente você deve usar um enum).

O ideal seria não só fazer esse construtor muito mais legível, mas também a forma como as mensagens são manipuladas (mensagens de grupo que são manipulados de forma semelhante em um único manipulador de eventos)

Talvez em vez de ter uma classe diferente para cada mensagem, use uma bandeira que identifica a mensagem.

Isso reduziria drasticamente o número de mensagens que você tem e aumento de manutenção. Meu palpite é que a maioria das classes de mensagem têm sobre a diferença zero.

É difícil escolher uma forma adicional de atacar isso, porque o resto da arquitetura é desconhecido (para mim).

Se você olhar para o Windows, por exemplo, não nativamente saber como lidar com cada mensagem que pode ser jogado sobre. Em vez disso, os manipuladores de mensagem subjacente registrar funções de retorno de chamada com o segmento principal.

Você pode ter uma abordagem semelhante. Cada classe de mensagem precisa saber como lidar com si mesmo e pode registar-se com a aplicação maior. Isto deve simplificar o código e se livrar do acoplamento forte.

Obviamente você está na necessidade de um mecanismo de envio: dependendo do evento que você recebe, você quer executar código diferente

.

Você parece estar usando o sistema de tipos para identificar os eventos, enquanto ele está realmente significou para apoiar polimorfismo. Como Chris Lively sugere, você poderia muito bem (sem abusar do sistema de tipo) usar um enumerate para identificar as mensagens.

Ou você pode abraçar o poder do sistema de tipos, e criar um objeto de registro onde cada tipo de evento é registrado (por uma instância estática, um arquivo de configuração ou qualquer). Em seguida, você poderia usar o Chain of Responsibility para encontrar o manipulador adequado. Ou o manipulador faz o próprio manuseio, ou pode ser uma fábrica, criando um objeto que manipula o evento.

O último método parece um pouco underspecified e overengineered, mas no caso de 99 tipos de evento (já), parece apropriado para mim.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top