Сцепление слишком высокое - как лучше спроектировать этот класс?

StackOverflow https://stackoverflow.com/questions/189156

  •  06-07-2019
  •  | 
  •  

Вопрос

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

Майкрософт.Ремонтопригодность :'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" содержат мало кода;обычно они передают работу функции в другом классе.

Как я могу улучшить этот класс с меньшим сцеплением?

Это было полезно?

Решение

Попросите классы, выполняющие работу, регистрироваться на интересующие их события ... an шаблон посредника событий .

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

Другие советы

Вы также могли бы использовать какой-нибудь фреймворк 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, связанный с МоК и DI:

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top