Простой инжектор: Factory классы, которым необходимо создавать классы с зависимостями

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

public class MyFactory
{
    public IMyWorker CreateInstance(WorkerType workerType)
    {
        if (workerType == WorkerType.A)
              return new WorkerA(dependency1, dependency2);

        return new WorkerB(dependency1);

    }
}

Итак, вопрос в том, где я могу получить эти зависимости.

Один из вариантов может заключаться в том, чтобы сделать их зависимыми от factory. например.

public class MyFactory
{
    private Dependency1 dependency1;
    private Dependency2 dependency2;

    public MyFactory(Dependency1 dependency1, Dependency2, dependency2)
    {
        this.dependency1 = dependency1; this.dependency2 = dependency2;
    }

    public IMyWorker CreateInstance(WorkerType workerType)
    {
        if (workerType == WorkerType.A)
              return new WorkerA(dependency1, dependency2);

        return new WorkerB(dependency1);

    }
}

Другим может быть регистрация типов рабочих и создание этих зависимостей factory например.

public class MyFactory
{
    private IWorkerA workerA;
    private IWorkerB workerB;

    public MyFactory(IWorkerA workerA, IWorkerB, workerB)
    {
        this.workerA = workerA; this.workerB = workerB;
    }

    public IMyWorker CreateInstance(WorkerType workerType)
    {
        if (workerType == WorkerType.A)
              return workerA;

        return workerB;

    }
}

С первым вариантом я чувствую, что я забираю зависимости рабочих в factory. При втором варианте рабочие создаются при создании factory.

Мысли?

Ответ 1

Это классический вопрос.

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

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

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

Ответ 2

Я согласен с @Phil, позволяя factory зависеть от контейнера в порядке, но в его ответе отсутствует один мир информации.

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

Независимо от того, является ли зависимость от контейнера реализацией анти-шаблона Service Locator, зависит от того, где этот потребитель определен. Марк Seemann объясняет это здесь:

Контейнер DI, инкапсулированный в корне композиции, не является сервисом Locator - это компонент инфраструктуры.

Таким образом, чтобы ваш factory был зависим от контейнера, это прекрасно, пока вы определяете эту реализацию MyFactory внутри своего состава root.

Когда вы это сделаете, вы скоро столкнетесь с проблемой, так как класс, определенный в корне композиции, не может ссылаться на остальную часть приложения. Но эту проблему легко решить, определив интерфейс IMyFactory в приложении и позволяя реализации factory реализовать этот интерфейс (так как вы должны в любом случае придерживаться Принцип инверсии зависимостей).

Итак, ваша регистрация станет примерно такой:

container.RegisterSingleton<IMyFactory, MyFactory>();

И реализация такая:

private sealed class MyFactory : IMyFactory
{
    private readonly Container container;

    public MyFactory(Container container)
    {
        this.container = container;
    }

    public IMyWorker CreateInstance(WorkerType workerType)
    {
        if (workerType == WorkerType.A)
              return this.container.GetInstance<IWorkerA>();

        return this.container.GetInstance<IWorkerB>();
    }
}

Ответ 3

Решение, по моему мнению, сильно зависит от времени жизни двух зависимостей. Могут ли эти зависимости распределяться между объектами, тогда вы можете зарегистрировать их в контейнере и передать их в factory и повторно использовать. Если каждый "продукт" factory должен иметь свой собственный экземпляр, то, возможно, вы могли бы рассмотреть возможность создания отдельного factory для этих зависимостей (конечно, если они принадлежат к одному и тому же "семейству" объектов) и передать его на ваш factory, и попросите экземпляр всякий раз, когда вы создаете экземпляр IMyWorker. В качестве альтернативы Вы можете использовать Builder вместо factory для создания каждой зависимости до создания конечного продукта - IMyWorker в вашем случае.

Передача контейнера считается запахом кода, если вы не внедряете корневой состав, например, в WCF.

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

Очень хорошая книга, говорящая о Dependency Injection, - это книга "Инъекция зависимостей в .NET" Марка Seemann, которую я рекомендую:)

Ответ 4

Пока этот вопрос субъективен (ответ будет также), я бы сказал, что ваш первый подход является подходящим.

Когда вы используете Dependency Injection, вам нужно понять, что такое фактическая зависимость. В этом случае WorkerA и WorkerB не являются зависимостями, но явно Dependency1 и Dependency2 являются зависимостями. В реальном сценарии я использовал этот шаблон в своих приложениях Micrsoft Prism.


Надеюсь, пример моего приложения даст вам лучшее представление о шаблоне для использования. Я использовал зависимость ILoggerFacade. У меня были некоторые модели представлений, которые находились в отдельной сборке (factory тоже на этой сборке). Мой индивидуальный IPlayerViewModel был not зависимостями (поэтому я не пошел вторым маршрутом).

ShellViewModel.cs:

[Export]
public sealed class ShellViewModel : NotificationObject
{
    public ShellViewModel()
    {
         Players = new ObservableCollection<IPlayerViewModel>();

         // Get the list of player models
         // from the database (ICollection<IPlayer>)
         var players = GetCollectionOfPlayerModels();

         foreach (var player in players)
         {
             var vm = PlayerViewModelFactory.Create(player);

             Players.Add(vm);
         }
    }

    [Import]
    private IPlayerViewModelFactory PlayerViewModelFactory { get; set; }

    public ObservableCollection<IPlayerViewModel> Players { get; private set; }
}

IPlayerViewModelFactory.cs

public interface IPlayerViewModelFactory
{
    IPlayerViewModel Create(IPlayer player);
}

IPlayer.cs

public interface IPlayer
{
    // Sport Enum
    Sport Sport { get; set; }
}

Отдельная сборка /PlayerViewModelFactory.cs

[Export]
public sealed class PlayerViewModelFactory : IPlayerViewModelFactory
{
    [Import]
    private ILoggerFacade Logger { get; set; }

    public IPlayerViewModel Create(IPlayer player)
    {
        switch (player.Sport)
        {
            case Sport.Basketball:
                return new BasketballViewModel(Logger, player);

            case Sport.Football:
                return new FootballViewModel(Logger, player);

            // etc...

            default:
                throw new ArgumentOutOfRangeException("player");
        }
    }
}