【问题标题】:Winforms IoC Container - How to handle concrete types with a presenter factoryWinforms IoC Container - 如何使用演示者工厂处理具体类型
【发布时间】:2016-05-03 04:39:11
【问题描述】:

背景

我正在使用带有 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 视图调用演示者工厂时,输入工厂的类型是 HomeView 类型,而不是 IHomeView。因此,应用程序会抛出异常,因为容器没有 HomeView 注册(只有 IHomeView)。我的演示者都有他们存储的视图引用的接口,因为我觉得这对于测试来说会更好。我该如何避免这种情况?

【问题讨论】:

    标签: c# winforms generics simple-injector


    【解决方案1】:

    为表单提供接口到实现的绑定没有用处,因为它们是表示技术的根类型。大多数演示技术无论如何都无法处理自定义抽象,这就是您将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&lt;IHomeView&gt; 并存储传入的依赖项。工厂不再需要,可以从您的代码中删除。

    在你的程序主中:

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

    工厂类现在被替换为组合根中的GetForm 方法;表单无权访问它。泛型类型允许我们解析正确的演示者,同时保持代码类型安全。

    【讨论】:

    • 谢谢@Steven,感谢您的宝贵时间。这一切都说得通。这导致了我一直在争论的另一件事。我的主要观点相当复杂,因此我正在考虑将我的演示者拆分为较小的演示者,每个演示者都专注于特定的功能区域。你在那里有什么想法?该计划不完全符合您的上述策略。相反,我可以创建一堆部分类文件,每个类文件实现不同的功能区域,也许还有一个代表性的接口。即IHomeViewIHomeScheduleView 等。那么每次视图仍然是一个演示者
    • 再一次,如果我为 HomeView 表单上显示的每个功能区域提供不同的服务,这意味着至少需要将 6 个服务注入 HomePresenter。这似乎引发了 SRP 问题……但是,我不知道如何避免这种情况。所有这些功能区域都需要在 MainView 上。
    • 好的,最后一个问题。由于 Presenter 引用在 View 中存储为 IPresenter&lt;IHomeView&gt;,而 View 引用在 Presenter 中存储为 IHomeView,这使得 View 和 Presenter 之间难以通信。以前,如果在视图上单击按钮,我只会从视图中调用presenter.HandleButtonClick()。这里的正确方法是什么?
    • @Andrew 用较小的部分组成视图很有意义,在这种情况下,每个“子”视图都有自己的演示者。当您在主视图中按“保存”时,所有视图都可以绑定到正在发送的相同 DTO/命令消息。演示者和视图都不应该知道彼此的实现。我认为通常是演示者将自己与视图抽象上的事件挂钩。
    • 我已经成功实施了这个策略。我遇到了一个问题。在视图接口中,我定义了视图实现的事件,然后演示者可以订阅。在此更改之前,我为演示者订阅了演示者构造函数中的事件。使用您的新实现,因为首先创建了演示者,然后将其注入到视图构造函数中,所以我无法在演示者构造函数中为演示者订阅 View 事件,因为视图不存在。你知道我该如何解决这个问题吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 2011-02-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多