5

Я смущен о внедрении впрыскивания зависимостей в одном конкретном примере.Работа с абстрактной фабрикой, которая вводится через контейнер DI

Предположим, у нас есть класс SomeClass, который имеет зависимость от типа IClassX.

public class SomeClass 
{ 
    public SomeClass(IClassX dependency){...} 
} 

Создания конкретных реализаций интерфейса IClassX зависит от выполнения параметра N.

С данным конструктором, я не могу настроить DI контейнера (Unity используется), потому что я не знаю, что реализация IClassX будет использоваться во время выполнения. Марк Семанн в своей книге «Инъекция зависимостей в .Net» предлагает использовать абстрактную фабрику в качестве параметра впрыска.

Теперь у нас есть SomeAbstractFactory, который возвращает реализации IClassX на основе runtime paramater runTimeParam.

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(){ } 

    public IClassX GetStrategyFor(int runTimeParam) 
    { 
     switch(runTimeParam) 
     { 
      case 1: return new ClassX1(); 
      case 2: return new ClassX2(); 
       default : return new ClassDefault(); 
     } 
    } 
} 

SomeClass теперь принимает ISomeAbstractFactory в качестве параметра впрыска:

public class SomeClass 
{ 
    public SomeClass(ISomeAbstractFactory someAbstractfactory){...} 
} 

И это прекрасно. У нас есть только один корневой состав, где мы создаем граф объектов. Мы настраиваем контейнер Unity для ввода SomeAbstractFactory в SomeClass.

Но давайте предположим, что классы ClassX1 и ClassX2 имеют свои собственные зависимости:

public class ClassX1 : IClassX 
{ 
    public ClassX1(IClassA, IClassB) {...} 
} 

public class ClassX2 : IClassX 
{ 
    public ClassX2(IClassA, IClassC, IClassD) {...} 
} 

Как разрешить IClassA, IClassB, IClassC и IClassD зависимостей?

1. Инъекции через SomeAbstractFactory конструктор

Мы можем придать конкретные реализации IClassA, IClassB, IClassC и IClassD к SomeAbstractFactory так:

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD) 
    {...} 
    ... 
} 

Единство контейнер будет использоваться в исходном составной корень, а затем использовать бедный человек DI для возврата бетона ClassX1 или ClassX2 на основе параметра runTimeParam

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD){...} 

    public IClassX GetStrategyFor(int runTimeParam) 
    { 
     switch(runTimeParam) 
     { 
      case 1: return new ClassX1(classA, classB); 
      case 2: return new ClassX2(classA, classC, classD); 
       default : return new ClassDefault(); 
     } 
    } 
} 

Проблема с этим подходом:

  • SomeAbstractFactory знает о зависимости, которые Don `т действительно принадлежат к нему.
  • Deeper графа объектов потребуется изменить оба SomeAbstractFactory конструктор и класс реализации
  • DI контейнер не будет использоваться для разрешения зависимостей, бедные DI должны мужские использовать

2. Явный вызов DI контейнер

Вместо «новичка» ClassX1 или ClassX2 мы разрешим их с помощью контейнера DI.

public class SomeAbstractFactory 
{ 
    public SomeAbstractFactory(IUnityContainer container){...} 

    public IClassX GetStrategyFor(int runTimeParam) 
    { 
     switch(runTimeParam) 
     { 
      case 1: return container.Resolve<IClassX>("x1"); 
      case 2: return container.Resolve<IClassX>("x2"); 
       default : return container.Resolve<IClassX>("xdefault"); 
     } 
    } 
} 

Проблемы с этим подходом:

  • DI контейнер передается в SomeAbstractFactory
  • Д.И. Метод Решимость не используется только в корне композиции (ServiceLocator анти-паттерна)

Есть ли еще более подходящий подход?

ответ

1

В примере ниже показано, как это сделать с Unity. This blog post объясняет это немного лучше, используя Windsor. Основополагающая концепция в точности одинакова для каждой, немного отличающаяся реализация.

Я предпочел бы, чтобы моя абстрактная фабрика получила доступ к контейнеру. Я рассматриваю абстрактный завод как способ предотвращения зависимости от контейнера - мой класс зависит только от IFactory, так что это только реализация фабрики, использующей контейнер. Castle Windsor идет еще дальше - вы определяете интерфейс для завода, но Windsor обеспечивает реальную реализацию. Но это хороший знак того, что один и тот же подход работает в обоих случаях, и вам не нужно менять заводский интерфейс.

В приведенном ниже подходе необходимо, чтобы класс, зависящий от фабрики, передавал некоторый аргумент, который позволяет фабрике определять, какой экземпляр будет создан. Завод собирается преобразовать это в строку, и контейнер будет соответствовать ему с именованным экземпляром. Этот подход работает как с Unity, так и с Windsor.

Выполнение этого класса в зависимости от IFactory не знает, что завод использует строковое значение для поиска нужного типа. В примере с Виндзором класс передает на фабрику объект Address, а фабрика использует этот объект для определения того, какой модуль проверки адресов должен использоваться на основе страны адреса. Никакой другой класс, но фабрика «знает», как выбирается правильный тип. Это означает, что если вы переключитесь на другой контейнер, единственное, что вам нужно изменить, это реализация из IFactory. Ничего, что зависит от IFactory, должно измениться.

Вот пример кода с помощью Unity:

public interface IThingINeed 
{} 

public class ThingA : IThingINeed { } 
public class ThingB : IThingINeed { } 
public class ThingC : IThingINeed { } 

public interface IThingINeedFactory 
{ 
    IThingINeed Create(ThingTypes thingType); 
    void Release(IThingINeed created); 
} 

public class ThingINeedFactory : IThingINeedFactory 
{ 
    private readonly IUnityContainer _container; 

    public ThingINeedFactory(IUnityContainer container) 
    { 
     _container = container; 
    } 

    public IThingINeed Create(ThingTypes thingType) 
    { 
     string dependencyName = "Thing" + thingType; 
     if(_container.IsRegistered<IThingINeed>(dependencyName)) 
     { 
      return _container.Resolve<IThingINeed>(dependencyName); 
     } 
     return _container.Resolve<IThingINeed>(); 
    } 

    public void Release(IThingINeed created) 
    { 
     _container.Teardown(created); 
    } 
} 

public class NeedsThing 
{ 
    private readonly IThingINeedFactory _factory; 

    public NeedsThing(IThingINeedFactory factory) 
    { 
     _factory = factory; 
    } 

    public string PerformSomeFunction(ThingTypes valueThatDeterminesTypeOfThing) 
    { 
     var thingINeed = _factory.Create(valueThatDeterminesTypeOfThing); 
     try 
     { 
      //This is just for demonstration purposes. The method 
      //returns the name of the type created by the factory 
      //so you can tell that the factory worked.     
      return thingINeed.GetType().Name; 
     } 
     finally 
     { 
      _factory.Release(thingINeed); 
     } 
    } 
} 

public enum ThingTypes 
{ 
    A, B, C, D 
} 

public class ContainerConfiguration 
{ 
    public void Configure(IUnityContainer container) 
    { 
     container.RegisterType<IThingINeedFactory,ThingINeedFactory>(new InjectionConstructor(container)); 
     container.RegisterType<IThingINeed, ThingA>("ThingA"); 
     container.RegisterType<IThingINeed, ThingB>("ThingB"); 
     container.RegisterType<IThingINeed, ThingC>("ThingC"); 
     container.RegisterType<IThingINeed, ThingC>(); 
    } 
} 

Вот несколько модульных тестов. Они показывают, что завод возвращает правильный тип IThingINeed после проверки того, что было передано его функции Create().

В этом случае (который может быть или не быть применимым), я также указал один тип по умолчанию. Если ничего не зарегистрировано в контейнере, который точно соответствует требованию, он может вернуть это значение по умолчанию. Это значение по умолчанию также может быть нулевым экземпляром без какого-либо поведения. Но весь этот выбор находится в конфигурации фабрики и контейнера.

[TestClass] 
public class UnitTest1 
{ 
    private IUnityContainer _container; 

    [TestInitialize] 
    public void InitializeTest() 
    { 
     _container = new UnityContainer(); 
     var configurer = new ContainerConfiguration(); 
     configurer.Configure(_container); 
    } 

    [TestCleanup] 
    public void CleanupTest() 
    { 
     _container.Dispose(); 
    } 

    [TestMethod] 
    public void ThingINeedFactory_CreatesExpectedType() 
    { 
     var factory = _container.Resolve<IThingINeedFactory>(); 
     var needsThing = new NeedsThing(factory); 
     var output = needsThing.PerformSomeFunction(ThingTypes.B); 
     Assert.AreEqual(output, typeof(ThingB).Name); 
    } 

    [TestMethod] 
    public void ThingINeedFactory_CreatesDefaultyTpe() 
    { 
     var factory = _container.Resolve<IThingINeedFactory>(); 
     var needsThing = new NeedsThing(factory); 
     var output = needsThing.PerformSomeFunction(ThingTypes.D); 
     Assert.AreEqual(output, typeof(ThingC).Name); 
    } 
} 

Этот же завод может быть реализован с использованием Виндзора, и завод в примере Виндзора может быть сделан в Unity.

Смежные вопросы