Winforms IoC 容器 - 如何使用演示工厂处理具体类型

Winforms IoC Container - How to handle concrete types with a presenter factory

背景

我正在使用具有 MVP 模式的 Winforms 创建应用程序。我使用 SimpleInjector 作为我的 IoC 容器。我的演示者继承自:

public interface IPresenter<TView>
{
    TView View { get; set; }
}

internal class HomePresenter : IPresenter<IHomeView>
{
    public IHomeView View { get; set; }

    ...
}

为了创建演示器,我决定使用演示器工厂,方法如下:

public static IPresenter<TView> CreateForView<TView>(TView view)
    {
        var presenter = _container.GetInstance<IPresenter<TView>>();
        presenter.View = view;
        return presenter;
    }

然后在每个视图中,视图通过调用演示器工厂创建自己的演示器:

_homeMainPresenter = (HomePresenter) presenterFactory.CreateForView<IHomeView>(this);
_homeMainPresenter.View = this;

在我的 Program.cs 文件中,我有:

    static void Main()
    {
        System.Windows.Forms.Application.EnableVisualStyles();
        System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);

        Bootstrap();

        System.Windows.Forms.Application.Run((HomeView)container.GetInstance<IHomeView>());
    }

    private static void Bootstrap()
    {
        // Create the container
        container = new Container();

        // Register types
        container.Register<IHomeView, HomeView>(Lifestyle.Singleton);
        container.Register<IPresenter<IHomeView>, HomePresenter>();
        ...

        // Verify the container
        container.Verify();
    }

问题

当从 HomeView 视图调用 Presenter 工厂时,输入工厂的类型是 HomeView 类型,而不是 IHomeView。所以,应用程序抛出异常,因为容器没有 HomeView 注册(只有 IHomeView)。我的演示者都有他们存储的视图引用的接口,因为我觉得这对于测试来说会更好。我该如何避免这种情况?

为表单设置接口到实现的绑定没有用,因为它们是表示技术的根类型。大多数表示技术无论如何都无法处理自定义抽象,这就是您将 IHomeView 转换回 HomeView 以允许将其传递给 Application.Run 方法的原因。

您可以改为执行以下操作,而不是从视图中解析演示者:

public interface IHomeView { }

public interface IPresenter<TView> {
    TView View { get; set; }
}

public class HomeView : Form, IHomeView
{
    private readonly IPresenter<IHomeView> presenter;

    public HomeView(IPresenter<IHomeView> presenter) {
        this.presenter = presenter;
        InitializeComponent();
    }
}

这里的表单被注入 IPresenter<IHomeView> 并存储传入的依赖项。工厂不再需要,可以从您的代码中删除。

并且在您的程序 main 中:

static void Main()
{
    System.Windows.Forms.Application.EnableVisualStyles();
    System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);

    Bootstrap();

    System.Windows.Forms.Application.Run(GetForm<HomeView, IHomeView>(container));
}

private static void Bootstrap()
{
    // Create the container
    container = new Container();

    // Register types
    // NOTE: We register HomeView as concrete type; not by its interface.
    container.Register<HomeView>(Lifestyle.Singleton);

    // Here we batch-register all presenters with one line of code and
    // since the forms depend on them, they need to be singletons as well.
    container.Register(typeof(IPresenter<>),  AppDomain.CurrentDomain.GetAssemblies(), 
        Lifestyle.Singleton);
    ...

    // Verify the container
    container.Verify();
}

private static TForm GetForm<TForm, TView>() where TForm : Form, TView
{
    var form = container.GetInstance<TForm>();
    container.GetInstance<IPresenter<TView>>().View = form;
    return form;
}

工厂 class 现在被作为组合根的一部分的 GetForm 方法替换;表单无权访问它。通用类型允许我们解析正确的呈现器,同时保持代码类型安全。