8

我习惯于 Web 应用程序中的 IoC/DI - 主要是 Ninject 和 MVC3。我的控制器是为我创建的,填充了所有的依赖项、子依赖项等。

但是,在胖客户端应用程序中情况有所不同。我必须创建自己的对象,或者我必须恢复到服务定位器风格的方法,在这种方法中我要求内核(可能通过一些接口,以允许可测试性)给我一个完整的对象与依赖关系。

但是,我已经看到有几个地方将 Service Locator 描述为反模式。

所以我的问题是 - 如果我想在我的胖客户端应用程序中从 Ninject 中受益,有没有更好/更合适的方法来获得这一切?

  • 可测试性
  • 适当的 DI / IoC
  • 尽可能少的耦合

请注意,我在这里讨论的不仅仅是 MVVM 以及将视图模型放入视图中。这具体是由需要从内核提供存储库类型对象触发的,然后从该存储库中获取注入功能的实体(数据当然来自数据库,但它们还需要一些对象作为参数,具体取决于状态世界,Ninject 知道如何提供)。我可以在不将存储库和实体都留下无法测试的混乱的情况下以某种方式做到这一点吗?

如果有任何不清楚的地方,请告诉我。谢谢!

编辑 7 月 14 日

我确信提供的两个答案可能是正确的。然而,我身体的每一根纤维都在与这种变化作斗争;其中一些可能是由于缺乏知识造成的,但还有一个具体原因是我无法看到这种做事方式的优雅之处;

我在最初的问题中没有很好地解释这一点,但问题是我正在编写一个库,该库将被几个(最初是 4-5 个,以后可能更多)WPF 客户端应用程序使用。这些应用程序都在相同的域模型等上运行,因此将它们全部保存在一个库中是保持 DRY 的唯一方法。然而,这个系统的客户也有可能编写他们自己的客户——我希望他们有一个简单、干净的库可以与之交谈。我不想强迫他们在他们的 Composition Root 中使用 DI(在他的书中使用 Mark Seeman 之类的术语)——因为与他们刚刚更新 MyCrazySystemAdapter() 并使用它相比,这使事情变得非常复杂。

现在,MyCrazySystemAdapter(之所以选择这个名字是因为我知道人们在这里会不同意我的观点)需要由子组件组成,并使用 DI 组合在一起。MyCrazySystemAdapter 本身不需要注入。它是客户端与系统对话时需要使用的唯一接口。因此,客户应该高兴地获得其中之一,DI 就像幕后发生的魔术一样,并且该对象由许多不同的对象使用最佳实践和原则组成。

我确实意识到这将是一种有争议的做事方式。但是,我也知道将成为此 API 客户的人。如果他们发现他们需要学习和连接 DI 系统,并在他们的应用程序入口点(组合根)中提前创建他们的整个对象结构,而不是新建一个对象,他们会给我中指和直接弄乱数据库并以您难以想象的方式搞砸事情。

TL;DR:提供结构合理的 API 对客户来说太麻烦了。我的 API 需要交付他们可以使用的单个对象——使用 DI 和适当的实践在幕后构建。现实世界有时会胜过将所有东西倒过来以忠于模式和实践的愿望。

4

2 回答 2

5

我建议看看像 Caliburn 这样的 MVVM 框架。它们提供与 IoC 容器的集成。


基本上,您应该在 app.xaml 中构建完整的应用程序。如果某些部分需要稍后创建,因为您还不知道在启动时创建它们的所有内容,那么将工厂作为接口(见下文)或 Func(见Ninject 支持 Func(自动生成的工厂)?)注入到类中需要创建这个实例。两者都将在下一个 Ninject 版本中得到原生支持。

例如

public interface IFooFactory { IFoo CreateFoo(); }
public class FooFactory : IFooFactory
{
    private IKernel kernel;
    FooFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public IFoo CreateFoo()
    {
        this.kernel.Get<IFoo>();
    }
}

请注意,工厂实现在逻辑上属于容器配置,而不是业务类的实现。

于 2011-07-10T22:59:04.800 回答
1

我对 WPF 或 MVVM 一无所知,但您的问题基本上是关于如何在不使用服务定位器(或直接使用容器)的情况下从容器中取出东西,对吧?
如果是,我可以给你举个例子。

关键是您改用工厂,它在内部使用容器。这样,您实际上只在一个地方使用容器。

注意:我将使用 WinForms 的示例,而不是绑定到特定容器(因为,正如我所说,我不知道 WPF ......而且我使用 Castle Windsor 而不是 NInject),但由于您的基本问题不是特别与 WPF/NInject 相关联,您应该很容易将我的答案“移植”到 WFP/NInject。

工厂长这样:

public class Factory : IFactory
{
    private readonly IContainer container;

    public Factory(IContainer container)
    {
        this.container = container;
    }

    public T GetStuff<T>()
    {
        return (T)container.Resolve<T>();
    }
}

您的应用程序的主要形式通过构造函数注入获取此工厂:

public partial class MainForm : Form
{
    private readonly IFactory factory;

    public MainForm(IFactory factory)
    {
        this.factory = factory;
        InitializeComponent();  // or whatever needs to be done in a WPF form
    }
}

容器在应用启动时被初始化,主窗体被解析(因此它通过构造函数注入获取工厂)。

static class Program
{
    static void Main()
    {
        var container = new Container();
        container.Register<MainForm>();
        container.Register<IFactory, Factory>();
        container.Register<IYourRepository, YourRepository>();

        Application.Run(container.Resolve<MainForm>());
    }
}

现在主窗体可以使用工厂从容器中获取诸如存储库之类的东西:

var repo = this.factory.GetStuff<IYourRepository>();
repo.DoStuff();

如果你有更多的表单并且想从那里使用工厂,你只需要将工厂注入到这些表单中,就像进入主表单一样,在启动时注册额外的表单并从工厂的主表单中打开它们.

这是你想知道的吗?


编辑:
鲁本,你当然是对的。我的错。
我的答案中的全部内容是我躺在某个地方的一个旧示例,但是当我发布答案时我很着急,并且没有足够仔细地阅读我的旧示例的上下文。

我的旧示例包括一个主窗体,您可以从中打开应用程序的任何其他窗体。就是工厂的用途,因此您不必通过构造函数注入将所有其他表单注入主表单。
相反,您可以使用工厂打开任何新表单:

var form = this.factory.GetStuff<IAnotherForm>();
form.Show();

当然,您不需要工厂只是为了从表单中获取存储库,只要将存储库通过构造函数注入传递给表单即可。
如果您的应用程序仅包含几个表单,则根本不需要工厂,您也可以通过构造函数注入传递表单:

public partial class MainForm : Form
{
    private readonly IAnotherForm form;

    // pass AnotherForm via constructor injection
    public MainForm(IAnotherForm form)
    {
        this.form = form;
        InitializeComponent();  // or whatever needs to be done in a WPF form
    }

    // open AnotherForm
    private void Button1_Click(object sender, EventArgs e)
    {
        this.form.Show();
    }
}

public partial class AnotherForm : Form
{
    private readonly IRepository repo;

    // pass the repository via constructor injection
    public AnotherForm(IRepository repo)
    {
        this.repo= repo;
        InitializeComponent();  // or whatever needs to be done in a WPF form

        // use the repository
        this.repo.DoStuff();
    }
}
于 2011-07-09T12:46:54.600 回答