Вот решение, которое я придумал, но у него есть некоторые недостатки. Вам придется решить, приемлемы ли недостатки для вашей ситуации. Однако преимущество заключается в том, что конфигурация основана на соглашении, и для добавления новых типов IMessage
и IMessageHandler
не потребуется дополнительная конфигурация Windsor.
Я использовал комбинацию TypedFactoryFacility
Виндзора вместе с пользовательской ITypedFactoryComponentSelector
. Используя фабрику, я убираю необходимость в вашем коде напрямую вызывать метод контейнера Resolve
. Когда класс должен получить IMessageHandler
на основе типа сообщения, он может просто зависеть от фабрики. Вот фабрика:
public interface IMessageHandlerFactory
{
IMessageHandler GetMessageHandler(string messageType);
}
Поскольку я использую TypedFactoryFacility
Виндзора, мне не нужно реализовывать этот метод, но когда дело доходит до вызова метода GetMessageHandler
, я "помогу" Виндзору выбрать правильный компонент, определив пользовательский компонент селектор:
public class HandlerTypeSelector : DefaultTypedFactoryComponentSelector
{
private readonly WindsorContainer container;
public HandlerTypeSelector(WindsorContainer container)
{
this.container = container;
}
protected override string GetComponentName(MethodInfo method, object[] arguments)
{
if (method.Name == "GetMessageHandler")
{
var type = arguments[0].ToString();
var messageHandlers = container.ResolveAll<IMessageHandler>();
var single = messageHandlers.SingleOrDefault(h => h.GetMessageType() == type);
if( single != null)
return single.GetType().FullName;
}
return base.GetComponentName(method, arguments);
}
}
Зарегистрировать все это так же просто, как:
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<IMessageHandlerFactory>().AsFactory(c => c.SelectedWith(new HandlerTypeSelector(container))),
AllTypes.FromThisAssembly().BasedOn<IMessage>().WithService.AllInterfaces(),
AllTypes.FromThisAssembly().BasedOn<IMessageHandler>().WithService.AllInterfaces()
);
Вот некоторый тестовый код, который я использовал для проверки:
var sampleMessage = new RegisterUserMessage();
var factory = container.Resolve<IMessageHandlerFactory>();
var handler = factory.GetMessageHandler(sampleMessage.MessageType);
И классы / интерфейсы, с которыми я тестировал:
public interface IMessage
{
string MessageType { get; }
}
public interface IMessageHandler
{
string GetMessageType();
}
public class RegisterUserMessage : IMessage
{
public string MessageType
{
get { return "RegisterUser"; }
}
}
public class RegisterUserMessageHandler : IMessageHandler
{
public string GetMessageType()
{
return "RegisterUser";
}
}
public class RemoveUserMessage : IMessage
{
public string MessageType
{
get { return "RemoveUser"; }
}
}
public class RemoveUserMessageHandler : IMessageHandler
{
public string GetMessageType()
{
return "RemoveUser";
}
}
Теперь, недостаток: внутри селектора компонентов я решаю все IMessageHandler
и затем решаю, какой из них использовать, основываясь на имени (так как я не назвал обработчики сообщений при регистрации их, они будут зарегистрированы в Виндзоре с их полным именем). Если ваши обработчики сообщений легковесны и / или их немного, или они статичны, это может не быть проблемой. Если они временные и / или имеют значительный код инициализации, их разрешение каждый раз, когда вам нужен один, может быть дорогостоящим. В этом случае я бы отказался от всего этого подхода и пошел бы с чем-то более сложным, но позволяющим разрешить один обработчик сообщений напрямую по имени.