7

我最近一直在涉足 IOC 容器(在我的例子中是 LightInject)。

我一直在阅读,您应该只需要在启动时使用容器一次,而无需在其他地方使用。这是我很难理解的。如果我只能在引导/启动方法中引用容器,那么如果类依赖于用户输入,如何在项目中的其他地方或运行时解决我需要的内容。

因此,在我的传统 Windows 窗体应用程序中,在表单加载时,我将按照以下代码引导 Lightinject。这只是一个任意的例子,它更像是我需要了解的前提。

我可能在这里完全错过了一些东西,或者只是没有得到它。但是我应该如何解决依赖关系,如果我不能使用/不应该引用或使用 Container.GetInstance/Resolve/{Choose IOC Syntax Here},并且只能在组合根中。

例如,假设我的表单上有两个按钮和一个 TextBox。第一个按钮给我一个 ILoader(下面的代码),第二个按钮加载一个文件查看器(ILoader,下面的代码),其文件名是输入到 winform 上的文本框中的文件名。

如果没有 IOC 容器,我会执行以下操作(假设将其放入 click 事件中)

按钮 1 单击事件:

ISplitText MyStringFunc =  new WhateverImplementsIt();

按钮 2(根据文本框输入获取文件阅读器)

ILoader MyLoader = new FileReaderImplementation(TextBox1.Text);

使用 LightInject,我肯定不得不执行以下操作:

Button1 点击:

ISplitText Splitter = Container.GetInstance<ISplitText>();

按钮 2 单击

var LoaderFunc = Container.GetInstance<Func<string, ILoader>>();
ILoader l2 = LoaderFunc(TextBox1.Text);            

我不正确吗?在一个大型项目中,我会有 Container.GetInstance,到处都是,在主表单文件和其他地方,所以我怎么能只在一个地方引用容器,以引导程序的形式,我错过了一个魔法一块拼图?

在我见过的所有示例应用程序中,这一切都在一个简单的控制台应用程序中完成,在 Main 函数中。所有这些应用程序都遵循以下格式:

Container = new Container();
Container.Register<IFoo,Foo>();
Container.Register<IBar,Bar();

var Resolved = Container.GetInstance<IFoo>();

嗯,我明白这一切,而且非常简单。一旦您开始为应用程序本身添加一些复杂性,我就迷失了如何在不使容器本身公开、静态或以某种方式、形状或形式访问然后调用 Container.GetInstance 的情况下获取实例在一百万个地方(显然,这是一个很大的禁忌)。请帮忙!干杯,

楚德

PS - 我不关心“抽象容器”本身。所以宁愿只专注于增加我对上述内容的理解。

public class BootStrapIOC
{
    public ServiceContainer Container;
    public BootStrapIOC(ServiceContainer container)
    {
        Container = container;
    }

    public void Start()
    {
        Container.Register<ISplitText, StringUtil>();
        Container.Register<string, ILoader>((factory, value) => new FileViewByFileName(value));


    }
}



//HUH? How can i NOT use the container??, in this case in the button_click
ILoader Loader = Container.GetInstance<Func<string, ILoader>>();
ILoader l2 = Loader(TextBox1.Text);            
ISplitText Splitter = Container.GetInstance<ISplitText>();

编辑#1

好的,所以,在重新阅读评论并在互联网上查看更多示例之后,我想我可能终于明白了。问题是(我认为)是我对“更高层次”的思考不够。我试图在我的 winforms 应用程序中解决我的依赖关系,在表单已经构建之后,在表单本身中。到了现实时,为时已晚。我并没有将“表单本身”视为另一个对象,它需要将其依赖项注入其中。

所以我现在在我的 Program.cs 中引导:

static class Program
{
    private static ServiceContainer Container;

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Container = new ServiceContainer();
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        BootStrapIOC Strap = new BootStrapIOC(Container);

        Strap.Start();
        //This magic line resolves EVERYTHING for me required by the Form
        var form = Container.GetInstance<Form1>();
        Application.Run(form);
        //Application.Run(new Form1());
    }
}

我现在的问题是,就winforms而言,我的思路现在是否正确。似乎更有意义,将我的方法更改为“更高”的链条并从 Program.cs 解决?

其次,我不确定这是否完全需要一个新问题,请告知,因为我是一个菜鸟。

我将如何设置工厂以返回对象的正确实例?原始评论之一表明这将是这种情况下的用法。让我们使用一个人为的例子。我需要一个对象,但直到运行时/用户输入才知道哪个对象。

我的想法:

BootStrap Container.Register();

工厂接口和实现:让我们也放入一些可选参数,因为我想知道这是否是正确/最好的方法?

public interface IFileViewerFactory
{
    ILoader GetFileViewer(string FileName, string Directory = null, bool CreatingDirectory = false);
}

public class FileViewerFactory:IFileViewerFactory
{
    public FileViewerFactory() { }

    public ILoader GetFileViewer(string FileName, string Directory = null, bool CreatingDirectory = false)
    {
        if (CreatingDirectory == false)
        {
            if (Directory == null)
                return new FileViewByFileName(FileName);
            else
                return new FileViewByDirectoryName(Directory, FileName);
        }
        else
            return new FileViewByDirectoryNameCreateDirectoryOptional(Directory, FileName, CreatingDirectory);
    }
}

形式:

public IFileViewerFactory FileViewerFactory { get; set; }

按钮点击:

ILoader FileLoader = FileViewerFactory.GetFileViewer(TxtBoxFileName.Text);

或者:

ILoader FileLoader = FileViewerFacotry.GetFileViewer(TxtBoxFileName.Text,TxtBoxDirectory.Text);

所以最后,我的问题是:

  1. 我的“更高层次”思维的新方式,以及从 Program.cs 引导现在是否正确
  2. 如何处理 LightInject 中的可选参数
  3. 我如何设置我的工厂是正确的方法吗?
  4. 让我们忘记工厂的琐碎性,而只是尝试研究问题的机制:)
4

1 回答 1

0

我知道回答一个超过一年的问题有点晚了,但让我试试。

这里的问题是你不希望你的容器出现在你的合成根之外的任何地方。在由多个程序集组成的复杂解决方案中,这意味着容器本身仅由最顶层程序集(Composition Root 所在的位置)引用。

但是应用程序堆栈通常很复杂,您可能有多个程序集,但您的依赖关系应该在整个应用程序中得到解决。

从历史上看,一种可能的方法是服务定位器模式。定位器下降到堆栈的最底部,并从那里提供解决依赖关系的服务。因此,它在堆栈中的任何位置都可用。

这种方法有两个缺点,首先是您的容器在堆栈的最底部被引用,即使您绕过它,您仍然可以在任何地方引用您的定位器。后者在大型应用程序中可能会很痛苦,因为您可能有一些不希望被迫引用定位器(或其他任何东西)的独立程序集。

最终的解决方案称为本地工厂(又名依赖解析器),它只负责创建少量的依赖服务。诀窍是在您的应用程序中拥有多个本地工厂

典型的设置是这样的。假设有一个程序集,称为它A,客户端将使用它来获取IServiceA. 该程序集仅包含两个:

  • 服务的接口(义务)——IServiceA
  • 本地工厂客户端将用于获取服务实例

仅此而已,没有其他参考,没有容器。目前甚至还没有实现。这里的诀窍是让工厂为实际的提供者开放——从某种意义上说,工厂甚至还不知道如何创建实例——它是组合根会告诉它的。

// Assembly A

public interface IServiceA
{
   ...
}

public class ServiceAFactory
{
    private static Func<IServiceA> _provider;

    public static void SetProvider( Func<IServiceA> provider )
    {
        _provider = provider;
    }

    public IServiceA Create()
    {
        return _provider();
    }
}

这里的提供者有一个功能契约,但它也可以表示为一个接口

仅此而已,虽然目前工厂还没有实现,但客户端代码突然变得非常稳定:

// client code to obtain IServiceA
var serviceA = new ServiceAFactory().Create();

再次注意这个程序集A的独立性。它没有其他引用,但它提供了一种获取服务实例的简洁方法。其他程序集可以引用此程序集而无需其他附加引用。

然后是Composition Root

在堆栈的最顶端,您的主程序集引用了程序集A和其他一些程序集,我们称它为AImpl包含服务接口的可能实现。

从技术上讲,服务的实现可以在与接口相同的程序集中,但它只会让事情变得更容易

组合根通过将工厂方法委托给程序集来创建工厂的提供者A

 // Composition Root in the top level module
 // Both assemblies
 //    * A     that contains IServiceA
 //    * AImpl that contains an implementation, ServiceAImpl
 // are referenced here 
 public void CompositionRoot()
 {
      ServiceAFactory.SetProvider( () =>
         {
             return new ServiceAImpl();
         } );
 }

从现在开始,提供者设置完毕,所有使用工厂的客户端代码都可以成功获取实例。

组合根还提供其他本地工厂的所有其他实现。组合根中有多个设置:

    SomeLocalFactoryFromAssemblyA.SetProvider( ... );
    AnotherLocalFactoryFromAssemblyB.SetProvider( .... );
    ...

那你的容器在哪里?

好吧,容器只是提供者的一种可能实现。它只会帮助而不是破坏。但是请注意,您甚至不必使用它,这是一种选择而不是义务。

 public void CompositionRoot()
 {
      var container = new MyFavouriteContainer();
      container.Register<IServiceA, ServiceAImpl>(); // create my registrations

      ServiceAFactory.SetProvider( () =>
         {
             // this advanced provider uses the container
             // this means the implementation, the ServiceAImpl,
             // can have possible further dependencies that will be
             // resolved by the container
             container.Resolve<IServiceA>();
         } );
 }

这是我所知道的最干净的设置。它具有所有所需的功能:

  • 它以一种干净的方式分离关注点
  • 除了服务合同和工厂之外,客户端实际上不需要任何其他依赖项
  • 客户甚至不知道有或将会有一个容器,实际上客户并不关心
  • 在测试环境中,无需任何容器即可轻松设置提供程序,以提供服务的静态模拟
  • 组合根在这里是一个真正的作曲家——它是你代码中唯一一个三者:接口、实现和容器相遇的地方
于 2019-05-21T12:46:35.377 回答