Лучшая таблица / enum управляемая система вызова методов - PullRequest
2 голосов
/ 18 октября 2008

Учтите, что я взаимодействую с внешней системой, которая отправит сообщение (таблица БД, очередь сообщений, веб-служба) в каком-либо формате. В «заголовке сообщения» есть «MessageType», который является числом от 1 до 20. MessageType определяет, что делать с остальной частью сообщения. Есть такие вещи, как новые, измененные, удаленные, отмененные ...

Первым делом я хотел настроить перечисление и определить все типы. Затем разберите число в тип enum. С его помощью в качестве enum я бы настроил типичную систему регистра переключателей и вызвал определенный метод для каждого из типов сообщений.

Одна большая проблема - это обслуживание.
Система выключатель / корпус громоздка и утомительна, но она действительно проста.
Различные системы таблиц / конфигураций могут быть затруднены для того, чтобы кто-то другой мог взломать и добавить новые сообщения или настроить существующие сообщения.

Для 12 или около того MessageTypes система переключатель / регистр кажется вполне разумной. Какова разумная точка отсечения для перехода на настольную систему?

Какие системы считаются лучшими для решения проблем такого типа?

Я устанавливаю тег для C # и Java здесь, потому что это определенно распространенная проблема. Есть много других языков с такой же проблемой.

Ответы [ 4 ]

5 голосов
/ 18 октября 2008

В Java вы можете сделать это перечислением и задать поведение различным значениям (хотя при 100 значениях я надеюсь, что каждый тип поведения будет кратким, вызывая «правильные» классы).

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

Сказав это, настройка карты будет столь же уродливой, как и оператор switch. Если каждый оператор switch представляет собой всего лишь один вызов метода, вы можете попробовать этот формат:

switch (messageType)
{
    case   0: HandleLogin(message);         break;
    case  50: SaveCurrentDocument(message); break;
    case 100: HandleLogout(message);        break;
}

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

1 голос
/ 18 октября 2008

Интерфейс обработчика такой:

interface MessageHandler {
   void processMessage(Message msg) throws Exception;
   int[] queryInterestingMessageIds();
   int queryPriority(int messageId); // this one is optional
}

Найдите, создайте экземпляр и зарегистрируйте ваши обработчики. Возможно, вы захотите использовать механизм, основанный на отражении, например ServiceLoader , Spring (явная конфигурация или сканирование пути к классам и возможно автоматическое подключение) или файл простых свойств

Регистрация должна передавать каждый обработчик в класс WhwhatManager, который будет внутренне содержать карту (или индексированную по идентификатору сообщения) наборов обработчиков. Если вы ожидаете, что у вас будет несколько обработчиков, вы можете использовать метод queryPriority (int) , чтобы разрешить порядок обработки (в противном случае вы можете рассматривать это как ошибку и выдавать исключение во время настройки). Рекомендуется НЕ использовать статическую карту для регистрации.

Если вы решите поддерживать несколько обработчиков для сообщения, вы можете захотеть их объединить. В таком случае один из способов - изменить подпись следующим образом:

 Message processMessage(Message msg, Message original) throws Exception;
1 голос
/ 18 октября 2008

Как насчет Dictionary<MessageType, ProcessMessageDelegate> для хранения этих методов по типам сообщений? Во время инициализации класса зарегистрируйте все методы в этом словаре. Затем вызовите соответствующий метод. Ниже приведен псевдокод:

  delegate void ProcessMessageDelegate(Message message)

  public class MyMessageProcessor
  {
    Dictionary<int, ProcessMessageDelegate> methods;

    public void Register( int messageType, 
                          ProcessMessageDelegate processMessage)
    {
      methods[messageType] = processMessage;
    }

    public void ProcessMessage(int messageType, Message message)
    {
      if(methods.ContainsKey(messageType))
      {
        methods[messageType](message);
      }
    }
  }

Чтобы зарегистрировать методы:

   myProcessor.Register(0, ProcessMessageOfType0);
   myProcessor.Register(1, ProcessMessageOfType1);
   myProcessor.Register(2, ProcessMessageOfType2);
   ...

Редактировать : Я понял, что Джон уже предлагает карту, которая делает мой ответ излишним. Но я не понимаю, почему статически построенная карта страшнее, чем случай переключения?

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

Вот как я это сделал в C #.

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

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

Начните с создания интерфейса для обработчика сообщений (назовем его IMessageHandler) и Enum для типа сообщения (назовем его MessageType).

Затем создайте класс с именем MessageHandlerAttribute:

public class MessageHandlerAttribute : System.Attribute
{
    public MessageType MessageType { get; set; }
}

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

[MessageHandler(MessageType=MessageType.Login)]
public class LoginMessageHandler : IMessageHandler
{
    ...
}

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

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

Теперь мы можем использовать Reflection для построения карты обработчиков сообщений во время выполнения:

using System.Reflection;
...
Assembly mhAssembly = Assembly.LoadFrom(mhAssemblyPath);
Dictionary<MessageType, IMessageHandler> mhMap = new Dictionary<MessageType, IMessageHandler>();
foreach (Type t in mhAssembly.GetExportedTypes())
{
   if (t.GetInterface("IMessageHandler") != null)
   {
      MessageHandlerAttribute list = (MessageHandlerAttribute[])t.GetCustomAttributes(
         typeof(MessageHandlerAttribute), false);
      foreach (MessageHandlerAttribute att in list)
      {
         MessageType mt = att.MessageType;
         Debug.Assert(!mhMap.ContainsKey(mt));
         IMessageHandler mh = mhAssembly.CreateInstance(
            t.FullName,
            true,
            BindingFlags.CreateInstance,
            null,
            new object[] { },
            null,
            null);
         mhMap.Add(mt, mh);
      }
   }
   // depending on your application, you might want to check mhMap now to make
   // sure that every MessageType value is in it.
}
return mhMap;

Теперь, когда вы получаете сообщение, вы можете обработать его следующим образом:

Debug.Assert(MhMap.ContainsKey(Message.MessageType));
IMessageHandler mh = MhMap[Message.MessageType];
mh.HandleMessage(Message);

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...