Winforms IOC容器 - 组合根

时间:2017-04-10 10:44:49

标签: c# dependency-injection inversion-of-control light-inject

我最近在与IOC容器(我的案例中为LightInject)进行了一些讨论。

我一直在读你只需要在启动时使用容器ONCE,而不是在其他地方使用容器。这就是我难以理解的。如果我只能在引导程序/启动方法中引用容器,那么如何在课程依赖于用户输入的情况下,如何在项目中或在运行时解析我需要的内容。

所以在我的传统Windows窗体应用程序中,在Form Load Say上,我会根据下面的代码启动Lightinject。这只是一个随意的例子,它更像是我需要解决的前提。

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

对于Instance,我说我的表单上有两个按钮和一个TextBox。第一个按钮为我提供一个ILoader(下面的代码),第二个按钮加载一个文件查看器(ILoader,代码下面),其文件名是输入winform文本框的内容。

如果没有IOC容器,我会执行以下操作(让我们假设它放在点击事件中)

按钮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,遍布整个地方,在主窗体文件和其他地方,所以我怎么能只在引导程序的形式中仅在1个位置引用容器,我错过了一个魔法一块拼图?

在我看到的所有示例应用程序中,所有应用程序都在一个简单的控制台应用程序中完成,在主要功能中。所有这些应用程序都遵循以下格式:

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

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

嗯,我理解这一切,而且非常简单。一旦你开始为应用程序本身添加一些复杂性,我就失去了如何获取实例而不使Container本身公开,或静态,或以某种方式,形状或形式访问然后在一百万个地方调用Container.GetInstance(显然,这是一个很大的不行)。请帮忙! 欢呼声,

CHUD

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

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

好的,所以,在重新阅读评论并在互联网上查看更多示例之后,我想我可能最终理解它。问题是(我认为)是我没有思考&#34;更高水平&#34;足够。我试图在我的winforms应用程序中解决我的依赖性,在表单已经构建之后,以及表单本身。实际上,到那时为时已晚。我没有查看&#34;表格本身&#34;作为另一个对象,需要将它的依赖注入其中。

所以我现在在我的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方面是否正确。它似乎更有意义,改变了我的方法,更高的&#34;链接和解析来自Program.cs ??

其次,我不确定这是否需要一个新的问题,请告知我是一个SO菜鸟。

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

我的想法:

自举     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. 是我的新方式&#34;更高水平&#34;思考,从Program.cs引导现在正确
  2. 如何处理LightInject中的可选参数
  3. 我是如何以正确的方式设置工厂的?
  4. 让我们忘记工厂的繁琐,并尝试研究问题的机制:)

1 个答案:

答案 0 :(得分:0)

我知道回答一个已有一年多的问题有点晚了,但让我尝试一下。

这里的问题是,您不希望容器超出“合成根”之外的任何其他位置。在由多重组合组成的复杂解决方案中,这意味着容器本身仅由 top 程序集(“ Composition Root”所在的位置)引用。

但是应用程序堆栈通常很复杂,您可能有多个组装,但仍然需要在整个应用程序中解决依赖问题。

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

这种方法有两个缺点,首先是您的容器在堆栈的最底部被引用,并且即使您绕行了容器,定位器仍然在各处被引用。后者在大型应用程序中可能会很痛苦,因为您可能有一些不想被强迫引用定位器(或其他任何东西)的独立汇编。

最终的解决方案称为 Local Factory (又名Dependency Resolver),它只负责创建其很少的dependand服务。然后,诀窍是在您的应用中拥有多个本地工厂

典型的设置是这样的。假设有一个程序集,称为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的独立性。它没有其他参考,仍然提供了一种获取服务实例的干净方法。其他程序集可以引用此程序集,而无需其他引用。

然后是组成根

在堆栈的最顶部,主程序集引用程序集A和其他程序集,我们将其称为AImpl,其中包含服务接口的可能实现。

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

Composition Root通过在堆栈中委托工厂方法到程序集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();
         } );
 }

从现在开始,将设置提供程序,并且使用工厂的堆栈中的所有客户端代码都可以成功获取实例。

Composition Root还提供其他本地工厂的所有其他实现。然后在“合成根目录”中有多种设置:

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

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

  • 它以清晰的方式分离关注点
  • 除了服务合同和工厂之外,客户实际上不需要任何其他依赖项
  • 客户甚至不知道有或将要有一个容器,实际上,客户不在乎
  • 在测试环境中,可以轻松地在没有任何容器的情况下设置提供程序,以提供服务的静态模拟
  • 这里的Composition Root是真正的作曲家-这是代码中唯一三个位置(接口,实现和容器)相遇的地方