Вот как я это сделал в 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);
Этот код основан на коде, который я сейчас имею в производственной системе; Я немного изменил его (чтобы обработчики сообщений реализовали интерфейс вместо наследования от абстрактного класса и чтобы он обрабатывал несколько атрибутов обработчика сообщений), что, вероятно, внесло в него ошибки.