Шаблон стратегии и зависимость от использования с помощью Unity

Я, наконец, намочил ноги с помощью инъекции зависимостей (долгое время); Я начал играть с Unity и столкнулся с проблемой с шаблоном стратегии. Я могу использовать контейнер для возврата ко мне конкретных реализаций стратегии, основанной на имени, но я не вижу, как я должен получить правильную стратегию в контексте. Проиллюстрируем на простом примере: контекст - это автомобиль, у которого есть IEngine (стратегия), с двумя реализациями, FastEngine и SlowEngine. Код будет выглядеть следующим образом:

public interface IEngine
{
    double MaxSpeed
    {
        get;
    }
}

internal class FastEngine:IEngine
{
    public double MaxSpeed
    {
        get 
        { 
            return 100d; 
        }
    }
}

internal class SlowEngine:IEngine
{
    public double MaxSpeed
    {
        get
        {
            return 10d;
        }
    }
}

public class Car
{
    private IEngine engine;
    public double MaximumSpeed
    {
        get
        {
            return this.engine.MaxSpeed;
        }
    }

    public Car(IEngine engine)
    {
        this.engine = engine;
    }
}

Моя проблема заключается в следующем: как мне нужно создавать экземпляр быстрого автомобиля или медленного автомобиля? Я могу использовать контейнер, чтобы предоставить мне каждую реализацию, и я могу установить реализацию по умолчанию:

IUnityContainer container = new UnityContainer();
container.RegisterType<IEngine, FastEngine>();
container.RegisterType<IEngine, FastEngine>("Fast");
container.RegisterType<IEngine, SlowEngine>( "Slow" );
var car = container.Resolve<Car>();
Assert.AreEqual(100, car.MaximumSpeed);

но я хотел бы иметь возможность запросить автомобиль с конкретной реализацией стратегии - что-то вроде

var car = container.Resolve<Car>(??? use "Fast" or "Slow ???);

Можно ли использовать контейнер для этого? Или я должен написать Factory, который использует контейнер? Любое руководство было бы оценено - я не уверен, что думаю об этом правильно!

Ответ 1

Общий шаблон в DI заключается в том, что во время выполнения будет единственная реализация данной абстракции. Это просто делает жизнь намного проще, так как вам не нужно иметь дело с двусмысленностью, такой как описываемая вами.

Однако иногда вам нужно изменить реализацию на основе контекста, например, пример, который вы даете. Многие контейнеры DI предоставляют способы, при которых вы можете предоставить квалификационный параметр, но это означает, что вы в конечном итоге плотно соедините свой код с конкретным контейнером DI.

Лучшим решением было бы ввести Аннотация Factory, который может предоставить то, что вам нужно. Что-то вроде

public interface ICarFactory
{
    Car Create(IEngine engine);
}

Если вам нужно добавить больше стратегий, возможно, дизайн Builder может поместиться еще лучше.

В любом случае, дело в том, что вместо того, чтобы регистрировать в контейнере много разных автомобилей, вместо этого вы должны зарегистрировать одну реализацию ICarFactory.

В вашем коде клиента вы должны использовать введенный ICarFactory для создания экземпляра Car, основанного на определенном IEngine.

var car = factory.Create(engine);