【问题标题】:Simple Injector: Factory classes that need to create classes with dependenciesSimple Injector:需要创建具有依赖关系的类的工厂类
【发布时间】:2013-11-26 12:13:12
【问题描述】:

我有一个工厂类,它创建了几种不同类型的类。工厂在容器中注册。考虑到它们也有依赖关系,在工厂内创建类的推荐方法是什么。我显然想避免对容器的依赖,但如果我新建这些类,那么它们将不会使用容器。 例如

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

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

一种选择可能是使它们成为工厂的依赖项。 例如

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

    }
}

另一种可能是注册工人类型并建立工厂的依赖关系 例如

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

使用第一个选项,我觉得我正在将工人的依赖项吸进工厂。使用第二个选项,工人是在创建工厂时创建的。

【问题讨论】:

  • 我喜欢您的第一个解决方案的外观,假设您可以找到产生依赖项的方法。您可能需要它们来自工厂。您的第二个解决方案不创建实例,因此不是工厂。
  • 在工厂的构造函数中注入所有工厂产品的好处是您可以免费获得依赖图验证,而当您让工厂从容器请求(未注册)服务时,情况并非如此.

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


【解决方案1】:

我同意@Phil 的观点,让工厂依赖容器是可以的,但他的回答中缺少一种信息。

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

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

封装在组合根中的 DI 容器不是服务 定位器 - 它是一个基础设施组件。

因此,只要您在composition root 中定义此MyFactory 实现 内,就可以让您的工厂依赖于容器。

当您这样做时,您很快就会遇到麻烦,因为无法从应用程序的其余部分引用组合根中定义的类。但是这个问题很容易通过在应用程序中定义一个IMyFactory接口并让你的工厂实现实现该接口来解决(无论如何你都应该这样做以遵守Dependency Inversion Principle)。

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

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 用于阐明工厂是组合根的一部分。
  • -1 我不认为这是正确的,因为如果有人查看您工厂的构造函数,它不会告诉他们太多。这个工厂就像一个可以解决和做任何事情的“神”类。它的意图和依赖关系不再清晰。
  • @CodeART 不正确。工厂的意图非常明确,它实现了 IMyFactory,因此可以创建给定 WorkerType-enum-value 的实例。根本不是“神”课。构造函数对应用程序的任何其他部分都不可见,因为它是合成根的一部分。因此,即使它是一个“神”类,应用程序的任何一个部分都无法看到或使用它。
  • @CodeART:我同意你在发送错误信息时要小心。这就是为什么我提到this post by Mark Seemann,它在解释差异方面做得非常好,但该链接可能会被忽略。希望这次谈话能让事情变得更清楚。
  • +1 欲了解更多信息,我在Implementing an Abstract Factory post 中介绍了这个特定主题。
【解决方案2】:

这是一个经典的问题。

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

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

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

【讨论】:

  • 有趣的是,在我读到反模式之前,我就是这样做的,所以我试图找到一种丢失容器引用的方法。然后我遇到了这个问题。
  • 我自己已经解决了这个问题,并尝试了其他方法,但这是我回来的方法,我对此非常满意。
  • 谢谢菲尔。我认为@Steven 的回复也让我更舒服,并有助于证明容器引用的合理性。
  • 如果容器在应用程序集中并且工厂在视图模型中,我如何从工厂引用容器?
  • 我不确定我是否了解您的设置。也许开始一个新问题?
【解决方案3】:

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

除非您像在 WCF 中那样实现组合根,否则传递容器会被视为代码异味。

如果你最终得到一个工厂在构造函数中包含许多依赖项,那么你应该将其视为一个提示,表明有问题 - 很可能是违反了单一职责原则 - 它知道的太多了 ;)

一本非常好的关于依赖注入的书是我推荐的 Mark Seemann 的书“.NET 中的依赖注入”:)

【讨论】:

  • 我在好几个地方看到过这本书。我得拿一份。
  • 我同意。强烈推荐这本书。
【解决方案4】:

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

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


希望我的应用程序示例能让您更好地理解要使用的模式。我使用了ILoggerFacade 依赖项。我有一些视图模型位于单独的程序集中(工厂也位于该程序集中)。我个人的 IPlayerViewModels 是 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");
        }
    }
}

【讨论】:

  • 谢谢。我担心我最终可能会在工厂中产生很多依赖项。我认为菲尔和史蒂文的回答解决了这个问题。
猜你喜欢
  • 1970-01-01
  • 2016-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 2014-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多