#c# #ioc-container #autofac #protobuf-net
#c# #ioc-контейнер #autofac #protobuf-net
Вопрос:
Проблема
У меня есть определение сообщения protobuf с MessageType
полем, которое является перечислением. Учитывая входящее сообщение protobuf, я хотел бы разрешить некоторые IMessageHandler
файлы из контейнера IoC на основе MessageType
. Проблема двоякая: как мне выразить MessageType
ограничение при написании IMessageHandler
и как мне разрешить только нужные обработчики из контейнера IoC?
Я использую Autofac, но мне интересно услышать решения для любого контейнера.
Мои мысли:
Для выражения ограничения я вижу два варианта: свойство или атрибут (не могу использовать общие, потому что это значение enum). Мне нравится свойство, потому что оно делает невозможным запись IMessageHandler
без указания ограничения, но недостатком является то, что его необходимо создать, прежде чем вы сможете увидеть значение свойства.
В моем текущем коде я использую подход свойств. Я разрешаю все IMessageHandler
и выполняю фильтрацию вручную, но, похоже, должен быть способ получше. Не то чтобы я слишком беспокоился о производительности, просто похоже, что я разрешаю экземпляры, которые не используются.
Обновить
Чтобы сделать это немного более понятным, вот что я сейчас делаю
public interface IMessageHandler
{
MessageType TargetType { get; }
void Handle(IMessage message);
}
public class SomeHandler : IMessageHandler
{
public MessageType TargetType
{
get { return MessageType.Type1; }
}
public void Handle(IMessage message)
{
// implementation
}
}
Поэтому, когда я получаю сообщение protobuf, я разрешаю все IMessageHandler
сообщения и вызываю те, которые TargetType
соответствуют MessageType
входящему сообщению.
Комментарии:
1. (вы упомянули protobuf-net, но мне не совсем понятен вариант использования здесь — я не могу четко представить, какова настройка, поэтому я не уверен, что посоветовать)
2. @Marc protobuf-net является своего рода вспомогательным средством для ответа на вопрос. Я включил тег: 1) Чтобы было ясно, что дженерики исключены из таблицы. Я не могу выполнить IMessageHandler<MessageType.Type1>, потому что ограничение обязательно является значением enum. 2) Возможно, я задаю неправильный вопрос, и есть лучший способ отправлять сообщения protobuf на основе перечисления.
Ответ №1:
Я предлагаю вам зарегистрировать один класс IMessageHandler-Factory с помощью autofac и реализовать метод на этой фабрике, который принимает ваше перечисление и возвращает правильную реализацию IMessageHandler.
Комментарии:
1. Хорошо, но если я чего-то не понимаю, это переносит мою проблему на реализацию factory. Как бы я реализовал фабрику? Мне все еще нужно разрешить все IMessageHandlers? Я не хочу менять фабрику при добавлении нового IMessageHandler.
2. Хм. Ну, вам нужно что -то изменить, когда вы добавляете новый обработчик. Конфигурация IoC? Фабрика? Какой-либо другой конфигурационный файл или база данных? Может быть, вы могли бы что-то сделать с app.config, чтобы сопоставить имена перечислений с именами классов? Я просто не знаю, думаю, вы можете записать перечисления в автоматическое разрешение.
3. IoC — это просто причудливый словарь, отображающий типы реализаций. Вы говорите о словаре, сопоставляющем значения перечисления реализациям. Как вы заполняете этот словарь с минимальными изменениями кода… Наверное, мне интересно, как часто вы будете?
4. В моем идеальном решении мне не нужно ничего менять при добавлении нового обработчика. IoC уже настроен для сканирования соответствующих сборок на наличие обработчиков сообщений. Я бы предпочел не поддерживать какие-либо сопоставления вручную.
Ответ №2:
Я понял, что сервисы с ключами Autofac могут мне помочь. Я думаю, что я собираюсь использовать этот подход.
- Используйте атрибут, чтобы объявить, в чем
MessageType
данныйIMessageHandler
объект заинтересован. - Регистрируйте каждый
IMessageHandler
введенный вMessageType
- Используйте
ResolveKeyed
, чтобы получить только теIMessageHandler
, которые меня интересуют int.
Приятно то, что если кто-то забудет использовать атрибут, мы можем перехватить это при сборке контейнера. Вот полный пример. Любые предложения приветствуются!
class Program
{
static IContainer container;
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(Program).Assembly)
.AssignableTo<IMessageHandler>()
.Keyed<IMessageHandler>(t => GetMessageType(t));
container = builder.Build();
InvokeHandlers(MessageType.Type1);
InvokeHandlers(MessageType.Type2);
Console.ReadKey();
}
static MessageType GetMessageType(Type type)
{
var att = type.GetCustomAttributes(true).OfType<MessageHandlerAttribute>().FirstOrDefault();
if (att == null)
{
throw new Exception("Somone forgot to put the MessageHandlerAttribute on an IMessageHandler!");
}
return att.MessageType;
}
static void InvokeHandlers(MessageType type)
{
using (var lifetime = container.BeginLifetimeScope())
{
// I'm impressed that Autofac knows what I mean here!
var handlers = lifetime.ResolveKeyed<IEnumerable<IMessageHandler>>(type);
foreach (var handler in handlers)
{
handler.Handle();
}
}
}
}
public enum MessageType
{
Type1,
Type2,
}
public interface IMessageHandler
{
void Handle();
}
public class MessageHandlerAttribute : Attribute
{
public MessageHandlerAttribute(MessageType messageType)
{
MessageType = messageType;
}
public MessageType MessageType { get; private set; }
}
[MessageHandler(MessageType.Type1)]
public class Handler1 : IMessageHandler
{
public void Handle()
{
Console.WriteLine("A handler for Type1");
}
}
[MessageHandler(MessageType.Type1)]
public class Handler2 : IMessageHandler
{
public void Handle()
{
Console.WriteLine("Another handler for Type1");
}
}
[MessageHandler(MessageType.Type2)]
public class Handler3 : IMessageHandler
{
public void Handle()
{
Console.WriteLine("A handler for Type2");
}
}
Комментарии:
1. Я предлагаю спрятать ваше собственное решение за
IMessageHandlerFactory
подходом, который предлагает n8wrl, потому что в противном случае вы получите зависимость от контейнера в вашем приложении. Это шаблон поиска служб, который часто считается антишаблоном. Было бы очень легко создать реализацию,IMessageHandlerFactory
которая вызываетlifetime.ResolveKeyed<IMessageHandler>(type)
для вас. Еще один совет: не разрешайтеIEnumerable<IMessageHandler>
, а простоIMessageHandler
и позвольте фабрике возвращать a,CompositeMessageHandler
который выполняетforeach
.2. Спасибо @Steven, ты сделал несколько хороших замечаний. Мой пример не предназначен для демонстрации хорошего IoC; Мне действительно просто интересно, как an
IMessageHandler
объявляет, чтоMessageType
его интересует, и как использовать Autofac для их регистрации и разрешения.