Simple Injector:需要创建具有依赖项的类的工厂类

时间:2013-11-13 18:24:11

标签: c# .net dependency-injection ioc-container simple-injector

我有一个工厂类,可以创建几种不同类型的类。工厂已在集装箱中注册。在工厂内部创建类的建议方法是什么,因为它们也有依赖项。我显然希望避免对容器的依赖,但如果我新建这些类,那么他们将不会使用容器。 e.g。

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

        return new WorkerB(dependency1);

    }
}

所以问题是我从哪里获得这些依赖项。

一种选择可能是使它们成为工厂的依赖关系。 e.g。

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);

    }
}

另一个可能是注册工作者类型并制作工厂的依赖关系 e.g。

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;

    }
}

有了第一个选项,我觉得我正在把工人的依赖性带到工厂里。使用第二个选项,在创建工厂时创建工作程序。

思想?

4 个答案:

答案 0 :(得分:16)

我同意@Phil,让工厂依赖集装箱是好的,但他的答案中缺少一点信息。

您可能试图阻止对容器的依赖,因为您试图远离Service Locator anti-pattern。我同意Service Locator是一种反模式,应该予以阻止。

对容器的依赖是否是Service Locator反模式的实现取决于此消费者的定义位置。 Mark Seemann解释了这个here

  

封装在Composition Root中的DI容器不是Service   定位器 - 它是基础设施组件。

只要您在<{3}} 中定义MyFactory实施,就可以让工厂依赖容器。

执行此操作时,您很快就会遇到麻烦,因为无法从应用程序的其余部分引用组合根中定义的类。但是,通过在应用程序中定义IMyFactory接口并让工厂实现实现该接口(正如您应该遵循composition root所做的那样),可以轻松解决该问题。

所以你的注册会变成这样:

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>();
    }
}

答案 1 :(得分:5)

这是一个经典问题。

如果不同实现的数量增加,那么您的两个解决方案都可能会出现问题,特别是如果每​​个实现的依赖关系有很多差异。最终可能会得到一个带有20个参数的构造函数。

我首选的实现是让工厂类引用容器,并以这种方式解析所需的实例。

有些人可能认为这并不比服务定位器反模式更好,但我觉得这个问题没有一个完美的解决方案,这样做对我来说似乎是最自然的。

答案 2 :(得分:3)

在我看来,解决方案在很大程度上取决于两个依赖项的生命周期。这些依赖项是否可以跨对象共享,然后您可以在容器中注册它们并将它们传递给工厂并重用它们。如果工厂的每个“产品”都应该拥有它自己的实例,那么也许您可以考虑为这些依赖项构建一个单独的工厂(当然,如果它们属于相同的“系列”对象)并将其传递给您的工厂,并询问对于每当您创建IMyWorker实例时的实例。或者,在创建最终产品之前,您可以考虑使用Builder而不是Factory来创建每个依赖项 - 在您的案例中为IMyWorker。

传递容器被认为是代码气味,除非您正在实现组合根,例如在WCF中。

如果您最终在构造函数中使用了许多依赖项的工厂,那么您应该将其视为提示,这是错误的 - 很可能是违反单一责任原则 - 它只是知道太多;)

一本关于依赖注入的非常好的书是由Mark Seemann撰写的“.NET中的依赖注入”一书,我建议:)

答案 3 :(得分:1)

虽然这个问题是主观的(答案也是如此),但我会说你的第一种方法是恰当的。

当您使用Dependency Injection时,您必须了解什么是实际的依赖关系。在这种情况下,WorkerAWorkerB不是真正的依赖关系,但显然Dependency1Dependency2是依赖关系。在现实世界中,我在Micrsoft Prism应用程序中使用了这种模式。


希望我的应用程序示例能让您更好地理解要使用的模式。我使用了ILoggerFacade依赖项。我有一些视图模型,它们位于一个单独的组件中(工厂也驻留在该组件中)。我的个人IPlayerViewModel不是依赖关系(这就是为什么我没有走第二条路线)。

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");
        }
    }
}