Внедрить различные реализации интерфейса для команды во время выполнения
У меня есть интерфейс в моем проекте, который реализует 2 класса:
public interface IService
{
int DoWork();
}
public class Service1:IService
{
public int DoWork()
{
return 1;
}
}
public class Service2:IService
{
public int DoWork()
{
return 2;
}
}
У меня есть обработчик команд, который также зависит от IService
:
public CommandHandler1:ICommandHandler<CommandParameter1>
{
IService _service;
public CommandHandler1(IService service)
{
_service = service
}
public void Handle()
{
//do something
_service.DoWork();
//do something else
}
}
public interface ICommandHandler<TCommandParameter>
where TCommandParameter :ICommandParameter
{
void Handle(TCommandParameter parameter);
}
public interface ICommandParameter
{
}
Я хочу ввести Service1
или Service2
в мой CommandHandler1
на основе выбора пользователя. предположим, что у меня есть enum
, и пользователь может выбрать из него значение:
public enum Services
{
Service_One,
Service_Two
}
Если пользователь выбирает Service_One
, я хочу вставить Service1
в мой обработчик команд, и если он выберет Service_Two
, я хочу добавить Service2
в обработчик команд.
Я знаю, что я могу использовать именованные экземпляры, а затем вызывать ObjectFactory.GetInstance<IService>().Named("Service1")
, например, но
Есть ли способ реализовать это с помощью StructureMap
и предотвратить использование шаблона Service Locator
?
Ответы
Ответ 1
Предотвращение построения графиков объектов с использованием условий выполнения. Графы объектов должны быть исправлены. Используйте определения времени выполнения, чтобы определить путь через граф объекта.
То, что вам кажется недостающим, - абстракция, которая позволяет делегировать запрос правильной реализации IService
; назовем его IServiceDispatcher
:
interface IServiceDispatcher {
int DoWork(Services data);
}
sealed class ServiceDispatcher : IServiceDispatcher {
private readonly IService service1;
private readonly IService service2;
// NOTE: Feel free to inject the container here instead, as long as
// this class is part of your composition root.
public ServiceDispatcher(IService service1, IService service2) {
this.service1 = service1;
this.service2 = service2;
}
public int DoWork(Services data) {
return this.GetService(data).DoWork();
}
private IService GetService(Services data) {
switch (data) {
case Services.Service_One: return this.service1;
case Services.Service_Two: return this.service2;
default: throw new InvalidEnumArgumentException();
}
}
}
Теперь ваш CommandHandler1
может зависеть от IServiceDispatcher
:
public CommandHandler1 : ICommandHandler<CommandParameter1> {
private readonly IServiceDispatcher serviceDispatcher;
public CommandHandler1(IServiceDispatcher serviceDispatcher) {
this.serviceDispatcher = serviceDispatcher;
}
public void Handle(CommandParameter1 commandParameter) {
//do something
this.serviceDispatcher.DoWork(commandParameter.Service);
//do something else
}
}
Заметьте, что IServiceDispatcher
- действительно уродливое имя, которое технически описывает, что происходит. Это плохая идея, потому что интерфейс должен функционально описывать то, что вы хотите. Но поскольку вы не предоставили какой-либо предметный контекст для своего вопроса, это лучшее имя, которое я могу придумать; -)
Ответ 2
Это может быть не лучший подход, но он должен работать.
Добавить свойство для каждой службы, которая указывает ServiceTypes
, который она представляет:
public interface IService
{
public ServiceTypes Type { get; }
public int DoWork();
}
Внедрить свойство в каждом классе:
public class Service1 : IService
{
public ServiceTypes Type { get { return ServiceTypes.Service_One; } }
public void DoWork()
{
return 1;
}
}
Затем зарегистрируйте все реализации вашей службы в контейнере и введите их в обработчик. Оттуда выберите реализацию на основе свойства из команды:
container.For<IService>().Use<Service1>("service1");
container.For<IService>().Use<Service2>("service2");
Добавьте требуемый ServiceType
в класс команд:
public class Command1
{
// Other command properties
public ServiceTypes Service { get; set; }
}
И в обработчике команд:
public class CommandHandler : ICommandHandler<Command1>
{
private readonly IEnumerable<IService> _services;
public CommandHandler(IService[] services)
{
_servies = services;
}
public void Handle(Command1 command)
{
var service = _services.Single(s => s.Type == command.Service);
service.DoWork();
}
}
Ответ 3
Я бы создал factory, который ссылается на IContext
и использует его для разрешения конкретной зависимости службы.
public interface ICommandFactory
{
Command1 CreateCommand(Services serviceType);
}
public class CommandFactory : ICommandFactory
{
private readonly IContext _context;
public CommandFactory(IContext context)
{
_context = context;
}
public Command1 CreateCommand(Services serviceType)
{
IService service;
switch(serviceType)
{
case Services.Service_One: service = _context.GetInstance<Service1>();
break;
case Services.Service_Two: service = _context.GetInstance<Service2>();
break;
default:
throw new ArgumentOutOfRangeException("serviceType", serviceType, null);
}
return new Command1(service);
}
}
Затем вы регистрируетесь и используете его следующим образом:
var container = new Container(_ =>
{
_.For<ICommandFactory>().Use(context=>new CommandFactory(context));
});
var factory = container.GetInstance<ICommandFactory>();
var command = factory.CreateCommand(Services.Service_One);
command.Handle();
Во-первых, ответственность за выбор правильного сервиса отделена от самой команды. Он также позволяет команде иметь разные зависимости от верхней части самой службы, просто вызовите _context.GetInstance<TypeOfDependency>()
.
Об этом будет то же самое, что и Locator. Основная проблема локатора сервисов заключается в том, что он скрывает зависимости. Это не так, потому что тот, кто вызывает команду, явно указывает зависимость от класса CommandFactory
. И если интерфейс введен для класса factory (превратив его в шаблон AbstractFactory
), то сама реализация может стать частью стратегии разрешения зависимостей. Например. он будет в том же самом месте, что и сама структура зависимостей. Благодаря этому в модели домена нет локатора сервисов (статический или интерфейс).