Winforms IOC 容器 - 组合根



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

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

因此,在我的传统Windows表单应用程序中,在表单加载上,我会按照以下代码引导Lightinject。这只是一个随意的例子,它更多的是我需要思考的前提。

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

例如,假设我的窗体上有两个按钮和一个文本框。第一个按钮为我提供了一个 ILoader(代码下方),第二个按钮加载一个文件查看器(ILoader,代码下方),其文件名是输入到 winform 文本框中的内容。

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

按钮 1 点击事件 :

ISplitText MyStringFunc =  new WhateverImplementsIt();

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

ILoader MyLoader = new FileReaderImplementation(TextBox1.Text);

使用LightInject,我肯定不得不做以下事情:

按钮1 点击:

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

按钮 2 点击

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

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

在我看到的所有示例应用程序中,它们都是在一个简单的控制台应用程序中完成的,在 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 应用程序中解决我的依赖关系,在表单已经构建之后,以及表单本身。在现实中,到那时为时已晚。我并没有将"表单本身"视为另一个对象,它需要注入其中的依赖项。

所以我现在在我的程序中引导.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());
}
}

我现在的问题是,就赢形而言,我现在的思路是否正确。这似乎更有意义,改变我"更高"链条的方法并从程序.cs解决??

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

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

我的想法:

启动 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. 我的"更高层次"思维的新方式,以及从程序引导.cs现在是否正确
  2. 如何在 LightInject 中处理可选参数
  3. 我如何设置我的工厂是正确的方法吗?
  4. 让我们忘记工厂的模糊性,只是尝试研究问题的机制:)

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

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

但是应用程序堆栈通常很复杂,您可能有多个集合,并且仍然应该在整个应用程序中解决您的问题。

从历史上看,一种可能的方法是服务定位器模式。定位器向下到堆栈的最底部,然后从那里提供解析依赖关系的服务。因此,它可以在堆栈的任何地方使用。

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

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

典型的设置是这样的。假设有一个程序集,将其称为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

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

组合根通过将工厂方法从堆栈向下委托给程序集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>();
} );
}

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

  • 它以干净的方式分离关注点
  • 客户端实际上不需要除服务协定和工厂之外的任何其他依赖项
  • 客户端
  • 甚至不知道有一个容器或将要有一个容器,事实上客户端并不关心
  • 在测试环境中,无需任何容器即可轻松设置提供程序,以提供服务的静态模拟
  • Composition Root 在这里是一个真正的作曲家 - 它是代码中唯一一个将接口、实现和容器这三个东西结合在一起的地方

相关内容

  • 没有找到相关文章

最新更新