Использование контейнера Ioc во время выполнения на фабрике для определения инициализации класса - PullRequest
2 голосов
/ 14 июля 2010

Это хороший шаблон?Мне кажется, что код фабрики знает о IUnityContainer ...

Моя основная потребность заключалась в том, чтобы разрешить процесс ICalculationRuleProcess во время выполнения в зависимости от идентификатора класса.Это может быть основано на чем-то отличном от идентификатора, я знаю об этом ... в основном у меня есть известный набор идентификаторов, с которыми мне нужно иметь дело, потому что я загрузил записи в базу данных вручную, и нет никакого способа редактироватьзаписей.С каждым идентификатором у меня есть связанный класс.У меня также есть различное количество параметров конструктора в каждом классе, который реализует ICalculationRuleProcess, поэтому использование контейнера IoC чрезвычайно полезно по сравнению с некоторыми сумасшедшими инструкциями switch и конструкторами переменных с использованием Activator.CreateInstance

Вот что я сделал:

  1. Зарегистрированный экземпляр IUnityContainer внутри самого контейнера.Я не был уверен, возможно ли это вообще, но это сработало.
  2. Зарегистрировал все классы ICalculationRuleProcess с уникальным идентификатором в регистрации (в основном только Id.ToString () каждого возможного DistributionRule)
  3. Создал фабрику для определения правильного ICalculationRuleProcess, и он использовал контейнер IoC для определения правильного класса для загрузки.
  4. Зарегистрировал фабричный класс (ICalculationRuleProcessFactory) в контейнере IoC
  5. Везде, где нужно было использовать ICalculationRuleProcess, я заставил класс взять ICalculationRuleProcessFactory в своем конструкторе и вызвать метод Create, чтобы выяснить, какой ICalculationRuleProcess использовать.

Код для фабрики находится здесь:

  public interface ICalculationRuleProcessFactory
  {
    ICalculationRuleProcess Create( DistributionRule distributionRule );
  }

  public class CalculationRuleProcessFactory : ICalculationRuleProcessFactory
  {
    private readonly IBatchStatusWriter _batchStatusWriter;
    private readonly IUnityContainer _iocContainer;

    public CalculationRuleProcessFactory(
      IUnityContainer iocContainer,
      IBatchStatusWriter batchStatusWriter )
    {
      _batchStatusWriter = batchStatusWriter;
      _iocContainer = iocContainer;
    }

    public ICalculationRuleProcess Create( DistributionRule distributionRule )
    {
      _batchStatusWriter.WriteBatchStatusMessage( 
        string.Format( "Applying {0} Rule", distributionRule.Descr ) );

      return _iocContainer.Resolve<ICalculationRuleProcess>(
        distributionRule.Id.ToString() );
    }
  }

Ответы [ 3 ]

3 голосов
/ 14 июля 2010

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

По сути, это не плохо, что ваша фабрика принимает зависимость от контейнера, особенно какинтерфейс.Учтите, что если у вас когда-либо было для изменения реализаций контейнера, вы могли бы создать реализацию IUnityContainer, которая вообще не использует Unity (просто перенаправьте все элементы интерфейса к их соответствующим методам в контейнере замены).

Если это действительно вас беспокоит, вы можете добавить еще один слой косвенности, создав независимый интерфейс IoC с необходимыми Register, Resolve, и т. Д. методами и создатьреализация, которая направляет их в Unity.

2 голосов
/ 13 августа 2010

Есть еще один способ добиться этого без заводской зависимости от IUnityContainer, что само по себе не плохо.Это просто другой способ думать о проблеме.

Процесс выглядит следующим образом:

  1. Зарегистрируйте все различные экземпляры ICalculationRuleProcess.
  2. Получить всезарегистрируйте ICalculationRuleProcess и создайте лямбда-сотворение для каждого.
  3. Зарегистрируйте ICalculationRuleProcessFactory со списком ICalculationRuleProcess лямбда-сотворений.
  4. В ICalculationRuleProcessFactory.Create верните нужный процесс.*

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

Итак, мы делаем здесь метод расширения, который возвращает все регистрации с их именами.

public class Registration<T> where T : class {
    public string Name { get; set; }
    public Func<T> CreateLambda { get; set; }

    public override bool Equals(object obj) {

        var other = obj as Registration<T>;
        if(other == null) {
            return false;
        }


        return this.Name == other.Name && this.CreateLambda == other.CreateLambda;
    }


    public override int GetHashCode() {
        int hash = 17;
        hash = hash * 23 + (Name != null ? Name.GetHashCode() : string.Empty.GetHashCode());
        hash = hash * 23 + (CreateLambda != null ? CreateLambda.GetHashCode() : 0);
        return hash;
    }


}

public static class UnityExtensions {
    public static IEnumerable<Registration<T>> ResolveWithName<T>(this UnityContainer container) where T : class {
        return container.Registrations
          .Where(r => r.RegisteredType == typeof(T))
          .Select(r => new Registration<T> { Name = r.Name, CreateLambda = ()=>container.Resolve<T>(r.Name) });
    }
}

 public class CalculationRuleProcessFactory : ICalculationRuleProcessFactory
  {
    private readonly IBatchStatusWriter _batchStatusWriter;
    private readonly IEnumerable<Registration<ICalculationRuleProcess>> _Registrations;

    public CalculationRuleProcessFactory(
      IEnumerable<Registration<ICalculationRuleProcess>> registrations,
      IBatchStatusWriter batchStatusWriter )
    {
      _batchStatusWriter = batchStatusWriter;
      _Registrations= registrations;
    }

    public ICalculationRuleProcess Create( DistributionRule distributionRule )
    {
      _batchStatusWriter.WriteBatchStatusMessage( 
        string.Format( "Applying {0} Rule", distributionRule.Descr ) );

      //will crash if registration is not present
      return _Registrations
        .FirstOrDefault(r=>r.Name == distributionRule.Id.ToString())
        .CreateLambda();
    }
  }

//Registrations
var registrations = container.ResolveWithName<ICalculationRuleProcess>(container);
container.RegisterInstance<IEnumerable<Registration<ICalculationRuleProcess>>>(registrations);

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

0 голосов
/ 13 августа 2010

Привет, Роб, я собираюсь использовать по существу тот же шаблон. У меня есть несколько типов элементов корзины покупок, которые должны быть связаны с их собственным конкретным набором экземпляров валидатора различного класса.

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

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

...