Слишком высокое сцепление - как лучше спроектировать этот класс? - PullRequest
8 голосов
/ 10 октября 2008

Запустив FxCop для моего кода, я получаю это предупреждение:

Microsoft. Ремонтопригодность: FooBar.ctor в сочетании с 99 разные виды из 9 разных Пространства имен. Переписать или изменить способ уменьшить его класс связи, или рассмотрите возможность переноса метода на один из других типов это тесно в сочетании с. Класс сцепления выше 40 указывает на плохую ремонтопригодность, а класс связи между 40 и 30 указывает на умеренную ремонтопригодность, и класс сцепления ниже 30 указывает на хорошую ремонтопригодность.

Мой класс - это посадочная зона для всех сообщений с сервера. Сервер может отправлять нам сообщения разных типов 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
}

Методы "HandleSignOut" и "HandleConnectionTest" содержат мало кода; они обычно передают работу функции из другого класса.

Как я могу улучшить этот класс с помощью более низкой связи?

Ответы [ 5 ]

15 голосов
/ 10 октября 2008

Имейте классы, которые выполняют работу, регистрируют события, в которых они заинтересованы ... шаблон брокера событий шаблон.

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);
      }
   }
}
5 голосов
/ 10 октября 2008

Вы также можете использовать своего рода IoC-инфраструктуру, например Spring.NET, для добавления словаря. Таким образом, если вы получаете новый тип сообщения, вам не нужно перекомпилировать этот центральный хаб - просто измените файл конфигурации. <Ч /> Долгожданный пример:

Создайте новое консольное приложение с именем Example и добавьте следующее:

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

И файл 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>

Выход:

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

Как видите, MessageBroker не знает ни об одном из обработчиков, а обработчики не знают о MessageBroker. Все сопоставление выполняется в файле app.config, поэтому, если вам нужно обработать новый тип события, вы можете добавить его в файл конфигурации. Это особенно хорошо, если другие команды определяют типы событий и обработчики - они могут просто скомпилировать свои вещи в dll, добавить их в свое развертывание и просто добавить отображение.

В словаре есть значения типа object вместо MessageHandler<>, потому что фактические обработчики не могут быть преобразованы в MessageHandler<EventArgs>, поэтому мне пришлось немного поработать над этим. Я думаю, что решение все еще чистое, и оно хорошо обрабатывает ошибки отображения. Обратите внимание, что вам также нужно будет ссылаться на Spring.Core.dll в этом проекте. Вы можете найти библиотеки здесь и документацию здесь . глава внедрения зависимостей имеет отношение к этому. Также обратите внимание, что для этого не нужно использовать Spring.NET - важная идея здесь - внедрение зависимостей. Каким-то образом нужно что-то сказать брокеру отправлять сообщения типа a в x, и использование контейнера IoC для внедрения зависимостей - хороший способ не дать брокеру не знать о x, и наоборот.

Некоторые другие вопросы SO, связанные с IoC и DI:

0 голосов
/ 10 октября 2008

Очевидно, что вам нужен механизм диспетчеризации: в зависимости от получаемого события вы хотите выполнить другой код.

Похоже, вы используете систему типов для идентификации событий, хотя на самом деле она предназначена для поддержки полиморфизма. Как предлагает Крис Лайвли, вы также можете (не злоупотребляя системой типов) использовать перечисление для идентификации сообщений.

Или вы можете воспользоваться мощью системы типов и создать объект реестра, в котором регистрируется каждый тип события (статический экземпляр, файл конфигурации или что-то еще). Тогда вы можете использовать шаблон цепочки ответственности, чтобы найти правильный обработчик. Либо обработчик сам выполняет обработку, либо это может быть Factory, создающий объект, который обрабатывает событие.

Последний метод выглядит немного недоопределенным и чрезмерно усиленным, но в случае с 99 типами событий (уже) он представляется мне подходящим.

0 голосов
/ 10 октября 2008

Я не вижу остальной части вашего кода, но я бы попытался создать гораздо меньшее число классов arg Event. Вместо этого создайте несколько похожих друг на друга данных с точки зрения содержащихся в них данных и / или способа обработки их позже и добавьте поле, которое сообщит вам, какой именно тип события произошел (вероятно, вам следует использовать перечисление).

В идеале вы не только сделаете этот конструктор намного более читабельным, но и то, как обрабатываются сообщения (групповые сообщения, которые обрабатываются аналогичным образом в одном обработчике событий)

0 голосов
/ 10 октября 2008

Возможно, вместо того, чтобы иметь разные классы для каждого сообщения, используйте флаг, который идентифицирует сообщение.

Это резко сократит количество ваших сообщений и повысит удобство обслуживания. Я предполагаю, что большинство классов сообщений имеют нулевую разницу.

Трудно выбрать дополнительный способ атаковать это, потому что остальная архитектура неизвестна (мне).

Если вы посмотрите на Windows, например, она изначально не знает, как обрабатывать каждое сообщение, которое может появиться. Вместо этого базовые обработчики сообщений регистрируют функции обратного вызова в основном потоке.

Вы могли бы использовать подобный подход. Каждый класс сообщений должен знать, как себя обрабатывать, и может регистрироваться в более крупном приложении. Это должно значительно упростить код и избавиться от жесткой связи.

...