Подтвердить что ты не робот

Factory с DI и Ioc

Я знаком с этими шаблонами, но до сих пор не знаю, как справиться с следующей ситуацией:

public class CarFactory
{
     public CarFactory(Dep1,Dep2,Dep3,Dep4,Dep5,Dep6)
     {

     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return new Car1(Dep1,Dep2,Dep3);
               break;

               case B:
                   return new Car2(Dep4,Dep5,Dep6);
               break;
            }
     }
}

В общем, проблема связана с количеством ссылок, которые нужно вводить. Будет еще хуже, когда появится больше автомобилей.

Первый подход, который приходит мне на ум, заключается в том, чтобы вводить Car1 и Car2 в конструктор factory, но он против подхода factory, потому что factory всегда возвращает тот же объект. Второй подход заключается в том, чтобы вводить servicelocator, но он все время противостоят. Как его решить?

Изменить:

Альтернативный способ 1:

public class CarFactory
{
     public CarFactory(IContainer container)
     {
        _container = container;
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return _container.Resolve<ICar1>();
               break;

               case B:
                     return _container.Resolve<ICar2>();
               break;
            }
     }
}

Альтернативный способ 2 (слишком сложный для использования из-за слишком большого количества зависимостей в дереве):

public class CarFactory
{
     public CarFactory()
     {
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return new Car1(new Dep1(),new Dep2(new Dep683(),new Dep684()),....)
               break;

               case B:
                    return new Car2(new Dep4(),new Dep5(new Dep777(),new Dep684()),....)
               break;
            }
     }
}
4b9b3361

Ответ 1

Наличие оператора switch в factory является запахом кода. Интересно, что вы, похоже, не сосредотачиваетесь на решении этой проблемы вообще.

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

Интерфейсы

public interface ICarFactory
{
    ICar CreateCar();
    bool AppliesTo(Type type);
}

public interface ICarStrategy
{
    ICar CreateCar(Type type);
}

Заводы

public class Car1Factory : ICarFactory
{
    private readonly IDep1 dep1;
    private readonly IDep2 dep2;
    private readonly IDep3 dep3;

    public Car1Factory(IDep1 dep1, IDep2 dep2, IDep3 dep3)
    {
        if (dep1 == null)
            throw new ArgumentNullException("dep1");
        if (dep2 == null)
            throw new ArgumentNullException("dep2");
        if (dep3 == null)
            throw new ArgumentNullException("dep3");

        this.dep1 = dep1;
        this.dep2 = dep2;
        this.dep3 = dep3;
    }

    public ICar CreateCar()
    {
        return new Car1(this.dep1, this.dep2, this.dep3);
    }

    public bool AppliesTo(Type type)
    {
        return typeof(Car1).Equals(type);
    }
}

public class Car2Factory : ICarFactory
{
    private readonly IDep4 dep4;
    private readonly IDep5 dep5;
    private readonly IDep6 dep6;

    public Car1Factory(IDep4 dep4, IDep5 dep5, IDep6 dep6)
    {
        if (dep4 == null)
            throw new ArgumentNullException("dep4");
        if (dep5 == null)
            throw new ArgumentNullException("dep5");
        if (dep6 == null)
            throw new ArgumentNullException("dep6");

        this.dep4 = dep4;
        this.dep5 = dep5;
        this.dep6 = dep6;
    }

    public ICar CreateCar()
    {
        return new Car2(this.dep4, this.dep5, this.dep6);
    }

    public bool AppliesTo(Type type)
    {
        return typeof(Car2).Equals(type);
    }
}

Стратегия

public class CarStrategy : ICarStrategy
{
    private readonly ICarFactory[] carFactories;

    public CarStrategy(ICarFactory[] carFactories)
    {
        if (carFactories == null)
            throw new ArgumentNullException("carFactories");

        this.carFactories = carFactories;
    }

    public ICar CreateCar(Type type)
    {
        var carFactory = this.carFactories
            .FirstOrDefault(factory => factory.AppliesTo(type));

        if (carFactory == null)
        {
            throw new Exception("type not registered");
        }

        return carFactory.CreateCar();
    }
}

Использование

// I am showing this in code, but you would normally 
// do this with your DI container in your composition 
// root, and the instance would be created by injecting 
// it somewhere.
var strategy = new CarStrategy(new ICarFactory[] {
    new Car1Factory(dep1, dep2, dep3),
    new Car2Factory(dep4, dep5, dep6)
    });

// And then once it is injected, you would simply do this.
// Note that you could use a magic string or some other 
// data type as the parameter if you prefer.
var car1 = strategy.CreateCar(typeof(Car1));
var car2 = strategy.CreateCar(typeof(Car2));

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

var strategy = new CarStrategy(new ICarFactory[] {
    new Car1Factory(dep1, dep2, dep3),
    new Car2Factory(dep4, dep5, dep6),
    new Car3Factory(dep7, dep8, dep9)
    });

var car1 = strategy.CreateCar(typeof(Car1));
var car2 = strategy.CreateCar(typeof(Car2));
var car3 = strategy.CreateCar(typeof(Car3));

Ответ 2

Я ответил на аналогичный вопрос некоторое время назад. В основном это все о вашем выборе. Вам нужно выбирать между verbosity (что дает вам больше помощи от компилятора) и автоматизацией, что позволяет писать меньше кода, но более подвержено ошибкам.

Это - мой ответ, поддерживающий многословие.

И этот также является хорошим ответом, который поддерживает автоматизацию.

ИЗМЕНИТЬ

Я считаю, что подход, который вы считаете неправильным, на самом деле является лучшим. Говоря правду, обычно там не будет так много зависимостей. Мне нравится этот подход, потому что он очень явный и редко приводит к ошибкам во время выполнения.

Альтернативный способ 1:

Это плохо. Это фактически локатор сервисов, который считается anti-pattern.

Альтернативный способ 2

Как вы писали, это нелегко использовать, если оно смешивается с IOC-контейнером. Однако в некоторых случаях может быть полезен аналогичный подход (плохой человек DI).

В целом, я бы не стал беспокоиться о наличии "многих" зависимостей на ваших заводах. Это простой, декларативный код. Требуется несколько секунд для записи и может сэкономить вам время на борьбу с ошибками во время выполнения.

Ответ 3

Отвечая на ваш комментарий о примере кода с помощью Composition Root. Вы можете создать следующее, и это не локатор сервисов.

public class CarFactory
{
    private readonly Func<Type, ICar> carFactory;

    public CarFactory(Func<Type, ICar> carFactory)
    {
       this.carFactory = carFactory;
    }

    public ICar CreateCar(Type carType)
    {
        return carFactory(carType);
 }

и вот как выглядит ваш Composition Root с помощью Unity DI container:

Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));

Ответ 4

Во-первых, у вас есть конкретный factory, контейнер IoC может быть альтернативой, а не чем-то, что поможет вам там.

Затем просто переформатируйте factory, чтобы не ожидать полного списка возможных параметров в конструкторе factory. Это основная проблема - почему вы передаете так много параметров, если метод factory не нуждается в них?

Я предпочел бы передать конкретные параметры методу factory

public abstract class CarFactoryParams { }

public class Car1FactoryParams : CarFactoryParams
{
   public Car1FactoryParams(Dep1, Dep2, Dep3) 
   { 
      this.Dep1 = Dep1;
      ...
}

public class Car2FactoryParams 
      ...

public class CarFactory
{
    public ICar CreateCar( CarFactoryParams params )
    {
        if ( params is Car1FactoryParams )
        {
            var cp = (Car1FactoryParams)params;
            return new Car1( cp.Dep1, cp.Dep2, ... );
        }
        ...
        if ( params is ...

Инкапсулируя список параметров в определенном классе, вы просто заставляете клиента предоставлять именно эти параметры, необходимые для конкретного вызова метода factory.

Изменить:, к сожалению, из вашего сообщения было непонятно, что это за Dep1,... и как вы их используете.

Я предлагаю следующий подход, который отделяет поставщика factory от фактической реализации factory. Этот подход известен как Локальный Factory шаблон:

public class CarFactory
{
   private static Func<type, ICar> _provider;

   public static void SetProvider( Func<type, ICar> provider )
   {
     _provider = provider;
   }

   public ICar CreateCar(type)
   {
     return _provider( type );
   }
}

Сам по себе factory не имеет какой-либо реализации, он здесь, чтобы установить основу для вашего API домена, где вы хотите, чтобы ваши экземпляры автомобилей были созданы только с этим API.

Затем в корне композиции (где-то рядом с начальной точкой приложения, где вы настраиваете свой фактический контейнер), вы настраиваете поставщика:

CarFactory.SetProvider(
    type =>
    {
        switch ( type )
        {
           case A:
             return _container.Resolve<ICar1>();
           case B:
             return _container.Resolve<ICar2>();
           ..
    }
);

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

Эта реализация в основном # 1 из вашего отредактированного вопроса, однако она не имеет каких-либо конкретных недостатков. Клиент по-прежнему вызывает:

var car = new CarFactory().CreareCar( type );

Ответ 5

Я бы подумал о том, чтобы дать зависимостям хорошую структуру, чтобы вы могли использовать что-то похожее на ответ Wiktor, но я бы отвлек сам Car factory. Затем вы не используете структуру if..then.

public interface ICar
{
    string Make { get; set; }
    string ModelNumber { get; set; }
    IBody Body { get; set; }
    //IEngine Engine { get; set; }
    //More aspects...etc.
}

public interface IBody
{
    //IDoor DoorA { get; set; }
    //IDoor DoorB { get; set; }
    //etc
}

//Group the various specs
public interface IBodySpecs
{
    //int NumberOfDoors { get; set; }
    //int NumberOfWindows { get; set; }
    //string Color { get; set; }
}

public interface ICarSpecs
{
    IBodySpecs BodySpecs { get; set; }
    //IEngineSpecs EngineSpecs { get; set; }
    //etc.
}

public interface ICarFactory<TCar, TCarSpecs>
    where TCar : ICar
    where TCarSpecs : ICarSpecs
{
    //Async cause everything non-trivial should be IMHO!
    Task<TCar> CreateCar(TCarSpecs carSpecs);

    //Instead of having dependencies ctor-injected or method-injected
    //Now, you aren't dealing with complex overloads
    IService1 Service1 { get; set; }
    IBuilder1 Builder1 { get; set; }
}

public class BaseCar : ICar
{
    public string Make { get; set; }
    public string ModelNumber { get; set; }
    public IBody Body { get; set; }
    //public IEngine Engine { get; set; }
}

public class Van : BaseCar
{
    public string VanStyle { get; set; } 
    //etc.
}

public interface IVanSpecs : ICarSpecs
{
    string VanStyle { get; set; }
}

public class VanFactory : ICarFactory<Van, IVanSpecs>
{
    //Since you are talking of such a huge number of dependencies,
    //it may behoove you to properly categorize if they are car or 
    //car factory dependencies
    //These are injected in the factory itself
    public IBuilder1 Builder1 { get; set; }
    public IService1 Service1 { get; set; }

    public async Task<Van> CreateCar(IVanSpecs carSpecs)
    {
        var van = new Van()
        {
           //create the actual implementation here.
        };
        //await something or other
        return van;
    }
}

Я не перечислил его, но теперь вы можете реализовать несколько типов автомобилей и соответствующие им фабрики и использовать DI, чтобы впрыскивать все, что вам нужно.

Ответ 6

Многие контейнеры DI поддерживают понятие именованных зависимостей.

например. (Синтаксис структуры структуры)

For<ICar>().Use<CarA>().Named("aCar");
Container.GetNamedInstance("aCar") // gives you a CarA instance

Если вы используете что-то вроде соглашения, правило, как имя происходит от конкретного типа автомобиля, у вас есть ситуация, когда вам больше не нужно прикасаться к factory при расширении системы.

Использование этого в factory является простым.

class Factory(IContainer c) {
  public ICar GetCar(string name) {
    Return c.GetNamedInstance(name);
  }
}