在 Silverlight 和 MVVM 中将 DI 容器的使用保留在组合根中

发布于 2024-08-20 06:05:49 字数 1691 浏览 6 评论 0原文

我不太清楚如何设计,因此我将对 DI 容器的引用保留在 Silverlight + MVVM 应用程序的组合根中。

我有以下简单的使用场景:有一个主视图(可能是一个项目列表)和一个用于打开单个项目的编辑视图的操作。因此,当用户执行操作(例如单击某个按钮)时,主视图必须创建并显示编辑视图。

为此,我有以下代码:

public interface IView
{
   IViewModel ViewModel {get; set;}
}

然后,对于我需要能够创建的每个视图,我有一个抽象工厂,如下所示 然后,

public interface ISomeViewFactory
{
   IView CreateView();
}

该工厂被声明为“父”视图模型的依赖项,如下所示:

public class SomeParentViewModel
{
   public SomeParentViewModel(ISomeViewFactory viewFactory)
   {
       // store it
   }

   private void OnSomeUserAction()
   {
      IView view = viewFactory.CreateView();
      dialogService.ShowDialog(view);
   }       
} 

所以一切都是好吧,直到这里,还看不到 DI 容器:)。现在是 ISomeViewFactory 的实现:

public class SomeViewFactory : ISomeViewFactory
{
    public IView CreateView()
    {
        IView view = new SomeView();
        view.ViewModel = ????   
    }
}

“???”部分是我的问题,因为视图的视图模型需要从 DI 容器中解析,以便注入其依赖项。我不知道如何在不依赖除组合根之外的任何地方的 DI 容器的情况下做到这一点。

一种可能的解决方案是依赖于注入工厂的视图模型,如下所示:

public class SomeViewFactory : ISomeViewFactory
{
    public SomeViewFactory(ISomeViewModel viewModel)
    { 
       // store it
    }

    public IView CreateView()
    {
        IView view = new SomeView();
        view.ViewModel = viewModel;
    }
}

虽然这有效,但它存在一个问题,因为整个对象图是“静态”连接的(即“父”)视图模型将获取 SomeViewFactory 的实例,后者将获取 SomeViewModel 的实例,并且这些实例将与“父”视图模型一起存在),注入的视图模型实现是有状态的,如果用户打开子视图两次,第二次视图模型将是相同的实例并具有之前的状态。我想我可以用“初始化”方法或类似的方法来解决这个问题,但它闻起来不太对劲。

另一种解决方案可能是包装 DI 容器并让工厂依赖于包装器,但它仍然是一个“伪装”的 DI 容器:)

ps:我当前的解决方案是工厂知道 DI-容器,只有它们和组合根具有这种依赖性。

It's not quite clear to me how I can design so I keep the reference to the DI-container in the composition root for a Silverlight + MVVM application.

I have the following simple usage scenario: there's a main view (perhaps a list of items) and an action to open an edit view for one single item. So the main view has to create and show the edit view when the user takes the action (e.g. clicks some button).

For this I have the following code:

public interface IView
{
   IViewModel ViewModel {get; set;}
}

Then, for each view that I need to be able to create I have an abstract factory, like so

public interface ISomeViewFactory
{
   IView CreateView();
}

This factory is then declared a dependency of the "parent" view model, like so:

public class SomeParentViewModel
{
   public SomeParentViewModel(ISomeViewFactory viewFactory)
   {
       // store it
   }

   private void OnSomeUserAction()
   {
      IView view = viewFactory.CreateView();
      dialogService.ShowDialog(view);
   }       
} 

So all is well until here, no DI-container in sight :). Now comes the implementation of ISomeViewFactory:

public class SomeViewFactory : ISomeViewFactory
{
    public IView CreateView()
    {
        IView view = new SomeView();
        view.ViewModel = ????   
    }
}

The "????" part is my problem, because the view model for the view needs to be resolved from the DI-container so it gets its dependencies injected. What I don't know is how I can do this without having a dependency to the DI-container anywhere except the composition root.

One possible solution would be to have either a dependency on the view model that gets injected into the factory, like so:

public class SomeViewFactory : ISomeViewFactory
{
    public SomeViewFactory(ISomeViewModel viewModel)
    { 
       // store it
    }

    public IView CreateView()
    {
        IView view = new SomeView();
        view.ViewModel = viewModel;
    }
}

While this works, it has the problem that since the whole object graph is wired up "statically" (i.e. the "parent" view model will get an instance of SomeViewFactory, which will get an instance of SomeViewModel, and these will live as long as the "parent" view model lives), the injected view model implementation is stateful and if the user opens the child view twice, the second time the view model will be the same instance and have the state from before. I guess I could work around this with an "Initialize" method or something similar, but it doesn't smell quite right.

Another solution might be to wrap the DI-container and have the factories depend on the wrapper, but it'd still be a DI-container "in disguise" there :)

ps: my current solution is that the factories know about the DI-container, and it's only them and the composition root that have this dependency.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

半夏半凉 2024-08-27 06:05:49

为了尽可能接近示例代码,您可以以 IViewPopulator 的形式引入另一个间接级别:

public interface IViewPopulator
{
    void Populate(IView view);
}

您现在可以像这样实现 SomeViewFactory:

public class SomeViewFactory : ISomeViewFactory
{
    private readonly IViewPopulator populator;

    public SomeViewFactory(IViewPopulator populator)
    {
        if (populator == null)
        {
            throw new ArgumentNullException("populator");
        }

        this.populator = populator;
    }

    public IView CreateView()
    {
        IView view = new SomeView();
        this.populator.Populate(view);
        return view;
    }
}

这将 View 的创建和 ViewModel 的填充分开,遵循单一责任原则。某种程度上,它也是一个服务聚合的例子。

现在,您可以将 IViewPopulator 实现为采用正常依赖关系的具体类型:

public class SomeViewPopulator : IViewPopulator
{
    private readonly IDependency dep;

    public SomeViewPopulator(IDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    public void Populate(IView view)
    {
        var vm = // Perhaps use this.dep to create an instance of IViewModel...
        view.ViewModel = vm;
    }
}

可能还有其他方法可以对 IView 和 IViewModel 之间的关系进行建模,但上述代表了一种可能的解决方案。

关键是不断提取抽象,直到每个抽象都有明确定义的职责。这个练习实际上并不是要使代码与容器无关,而是最终要遵守 SOLID 原则。

To stay as close to your example code as possible, you can introduce yet another level of indirection in the form of an IViewPopulator:

public interface IViewPopulator
{
    void Populate(IView view);
}

You can now implement your SomeViewFactory like this:

public class SomeViewFactory : ISomeViewFactory
{
    private readonly IViewPopulator populator;

    public SomeViewFactory(IViewPopulator populator)
    {
        if (populator == null)
        {
            throw new ArgumentNullException("populator");
        }

        this.populator = populator;
    }

    public IView CreateView()
    {
        IView view = new SomeView();
        this.populator.Populate(view);
        return view;
    }
}

This separates the creation of Views and the population of ViewModels, adhering to the Single Responsibility Principle. To a certain extent, it is also an example of Service Aggregation.

You can now implement IViewPopulator as a concrete type that takes normal dependencies:

public class SomeViewPopulator : IViewPopulator
{
    private readonly IDependency dep;

    public SomeViewPopulator(IDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    public void Populate(IView view)
    {
        var vm = // Perhaps use this.dep to create an instance of IViewModel...
        view.ViewModel = vm;
    }
}

There are likely to be other ways you could model the relationship between IView and IViewModel, but the above represents one possible solution.

The key is to keep extracting abstractions until each have a well-defined responsibility. This exercise is really not about making code Container-agnostic at all, but eventually about adhering to SOLID principles.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文