Внедрить различные реализации интерфейса для команды во время выполнения

У меня есть интерфейс в моем проекте, который реализует 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), то сама реализация может стать частью стратегии разрешения зависимостей. Например. он будет в том же самом месте, что и сама структура зависимостей. Благодаря этому в модели домена нет локатора сервисов (статический или интерфейс).