WinForms应用程序中基于ApplicationController和IoC的MVP的实现

下午好!

在本文中,我将讨论如何在Windows Forms应用程序中实现MVP模式,并描述使用IoC和ApplicationController的实际情况和功能。从后台代码转换为MVP可以让我:

  • 由于更好的代码分离(SRP),提高了可读性-将BL与View分开;
  • 开发一种方法,以进一步扩展应用程序的功能;
  • 摆脱单例,这是我以前使用过的应用程序设置。

关于应用


一个用于管理VC组的应用程序。允许您用待处理的帖子填充群组。目前的主要功能是将带有图片或视频,主题标签,民意调查,地理位置的待处理帖子加载到VK组中,并能够配置发布时间和每天的帖子数量。目前,该应用程序只有一种形式。

随着其功能的增长,大量的代码被积累起来,这开始混淆这个事实,即表单变得非常重载并包含所有内容。在计划该项目的进一步开发时,我无法想象以同样的精神可以继续进行什么工作,现在是时候了,主要任务不是完善功能,而是重构。我开始寻找一种解决方案,该解决方案将有助于优化和拆分代码,并总体上改善应用程序体系结构,从而使其工作起来更加愉悦。

资源

重构解决方案


解决方案是实施MVP模式。作为基础,我采用了Windows窗体的MVP实现功能文章

本文分析了具有3种形式的简单应用程序的扩展示例:2个主模式和1个模式。本文讨论了一种非常高级的方法:

  • 除了ApplicationController和IoC,还在那里使用了Adapter,从而允许使用不同的IoC。
  • 3种形式:带参数,无参数和模态;
  • DIP原理被广泛应用。

在我的项目中,我只使用一种不带参数的形式,放弃了适配器(遵循YAGNI原则),因为IoC Lightinject对我来说足够了,并在较小程度上使用DIP简化了项目。

MVP实施


MVP(Model-View-Presenter)是一种设计模式,旨在方便将业务逻辑与显示方式分开。您可以在上面的文章中详细了解该理论。我将描述实现中的组件:

  • 模型是在View和Presenter之间传输的数据结构,并且包含用于显示和逻辑执行的数据。就我而言,模型是“设置”。当项目启动时,设置将被加载到MainFormView中,而当加载开始时,MainFormView将检查Settigns并将其传递给Presenter,以便Presenter在其一侧运行逻辑。
  • 视图是一种向用户显示数据的形式。就我而言,这是来自“设置”模型的数据,并且View还提供事件,以便Presenter将View与BL关联。

MainFormView实现了所有View通用的通用IView接口

    public interface IView
    {
        void Show();

        void Close();
    }

以及专用于此View的私有IMainFormView接口。一开始,我曾考虑过放弃它,但是如果将Presenter直接与表单相关联,那么在使用这样的View时,将提供针对Form的整套方法,这很不方便。

    public interface IMainFormView: IView
    {
        void LoadSettings(Settings settings);

        void UpdateSettings(Settings settings);

        void ShowMessage(string message);

        void LoadGroups(List<Group> groups);

        void EnableVKUploadGroupBox();

        bool Check();

        event Action Login;

        new event Action Close;

        event Action VKUpload;
    }

MVP的另一项创新是,已在表单中替换了Show方法,并通过构造函数将ApplicationContext传递给了表单,因此当从表单切换到表单并关闭时,将重新分配主表单。

        protected ApplicationContext _context;

        public MainForm(ApplicationContext context)
        {
            _context = context;
            InitializeComponent();

            dateTimePickerBeginDate.Format = DateTimePickerFormat.Custom;
            dateTimePickerBeginDate.CustomFormat = "MM/dd/yyyy hh:mm:ss";

            buttonAuth.Click += (sender, args) => Invoke(Login);
            this.FormClosing += (sender, args) => Invoke(Close);
            buttonLoad.Click += (sender, args) => Invoke(VKUpload);
        }

        public new void Show()
        {
            _context.MainForm = this;
            Application.Run(_context);
        }

- 主持人是一个封装观,服务和业务逻辑(BL),与它的组织观与服务之间的交互帮助的类。 BL主要在View事件处理程序中实现。与以前使用的CodeBehind不同,在MVP中,执行BL的事件处理程序显示在Presenter中,为简单起见,View中的事件以Action的形式显示,不带参数。处理程序通过public方法从表单获取的模型中接收执行所需的所有数据。

Presenter包含Run方法,该方法由ApplicationController调用,并启动表单:

    public interface IPresenter
    {
        void Run();
    }

ApplicationController-整个应用程序的单点控制和执行。自身封装了所有逻辑:IoC,演示者,视图,服务。

管理通过Run方法调用相应的Presenter。所有演示者都通过ApplicationController相互连接,该演示者在构造函数中接收该ApplicationController。因此,演示者可以通过调用Run方法来调用另一个演示者,该方法在内部调用IoC容器以获取所需的演示者并启动它。

    public class ApplicationController
    {
        ServiceContainer _container;

        public ApplicationController(ServiceContainer serviceContainer)
        {
            _container = serviceContainer;
            _container.RegisterInstance<ApplicationController>(this);
        }

        public void Run<TPresenter>() where TPresenter:class, IPresenter
        {
            var presenter = _container.GetInstance<TPresenter>();
            presenter.Run();
        }
    }

IoC容器是应用程序逻辑中使用的所有“依赖项”的聚集器。它包含了:

  • 查看构造函数
  • 建设者介绍人
  • 服务实例
  • 应用环境
  • 应用控制器

在启动期间,所有依赖项都添加到了容器中,这可以在Program.cs文件中看到:

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

            ulong appid = ulong.Parse(ConfigurationManager.AppSettings["AppIdForTest"]);
            VKGroupHelperWorker vk = new VKGroupHelperWorker(appid);


            ServiceContainer container = new ServiceContainer();
            container.RegisterInstance<VKGroupHelperWorker>(vk);
            container.RegisterInstance<Settings>(Globals.Settings);
            container.RegisterInstance<ApplicationContext>(Context);
            container.Register<IMainFormView,MainForm>();
            container.Register<MainFormPresenter>();

            ApplicationController controller = new ApplicationController(container);
            controller.Run<MainFormPresenter>();
        }

对于IoC,我使用了Lightinject组件,该组件必须在使用前通过NPM安装。

因此,容器可以包含对象构造函数和对象本身,就像使用Settings和VKGroupHelperWorker(VK API客户端)所做的那样,形成所有使用的应用程序资源的集合。容器的一个有用功能是,所有这些嵌入式资源,类都可以通过构造函数参数来获取。例如,
ApplicationController,IMainFormView,VKGroupHelperWorker-以前实现的依赖项,可以是对象构造函数和实例。如果实现了一个实例,则所有生成的对象将与同一个实例一起使用,这使您可以摆脱使用单例模式的情况。

public MainFormPresenter(ApplicationController applicationController, IMainFormView mainForm, Settings settings, VKGroupHelperWorker vk)
        {
            _view = mainForm;
            _settings = settings;
            _vk = vk;

            _view.Login += () => Login();
            _view.Close += () => Close();
            _view.VKUpload += () => VKUpload();
        }

MVP的实施使我能够:

  • 部分摆脱了Singleton,我以前曾用过它来处理应用程序设置;
  • 将BL与View分开,从而改善代码分离(SRP)
  • 开发一种在不使视图混乱的情况下进一步扩展应用程序的方法。

有关已完成操作的更多信息,请参见项目存储库

All Articles