Implementierung von MVP basierend auf ApplicationController und IoC in einer WinForms-Anwendung

Guten Tag!

In diesem Artikel werde ich darüber sprechen, wie ich das MVP-Muster in meiner Windows Forms-Anwendung implementiert habe, und praktische Situationen und Funktionen der Verwendung von IoC und ApplicationController beschreiben. Der Wechsel von Codebehind zu MVP ermöglichte mir:

  • Verbesserung der Lesbarkeit durch bessere Codetrennung (SRP) - BL von Ansicht trennen;
  • eine Methodik zur weiteren Erweiterung der Funktionalität der Anwendung zu entwickeln;
  • Singleton loswerden, mit dem ich früher mit Anwendungseinstellungen gearbeitet habe.

Über App


Eine App zur Verwaltung von VC-Gruppen. Ermöglicht das Füllen einer Gruppe mit ausstehenden Posts. Die Hauptfunktionalität im Moment ist das Laden ausstehender Beiträge in die VK-Gruppe mit Bildern oder Videos, Hashtags, Umfragen, geografischem Standort und der Möglichkeit, die Veröffentlichungszeit und die Anzahl der Beiträge pro Tag zu konfigurieren. Derzeit hat der Antrag ein Formular.

Mit dem Wachstum seiner Funktionalität sammelte sich viel Code, was die Tatsache verwirrte, dass das Formular sehr geladen wurde und alles in sich enthielt. Bei der Planung der Weiterentwicklung des Projekts konnte ich mir nicht vorstellen, was im gleichen Sinne weitergeführt werden könnte, und jetzt kam der Moment, in dem die Hauptaufgabe nicht darin bestand, die Funktionalität zu verfeinern, sondern das Refactoring. Und ich suchte nach einer Lösung, mit der der Code optimiert und aufgeteilt und die Anwendungsarchitektur generell verbessert werden kann, damit die Arbeit damit angenehmer wird.

Quelle

Refactoring-Lösung


Die Lösung bestand darin, das MVP-Muster zu implementieren. Als Grundlage habe ich den Artikel MVP-Implementierungsfunktionen für Windows Forms verwendet .

Der Artikel analysiert ein erweitertes Beispiel einer einfachen Anwendung mit 3 Formularen: 2 Haupt- und 1. Modal. Der Artikel beschreibt einen sehr fortgeschrittenen Ansatz:

  • Neben ApplicationController und IoC wird dort auch der Adapter verwendet, mit dem verschiedene IoCs verwendet werden können.
  • 3 Arten von Formularen: mit Parametern, ohne Parameter und modal;
  • Das DIP-Prinzip ist weit verbreitet.

In meinem Projekt verwende ich nur ein Formular ohne Argumente, habe den Adapter aufgegeben (nach dem YAGNI-Prinzip), da IoC Lightinject für mich ausreicht, und verwende in geringerem Maße DIP, um das Projekt zu vereinfachen.

MVP-Implementierung


MVP (Model-View-Presenter) ist ein Entwurfsmuster, mit dem die Geschäftslogik von der Art und Weise, wie sie angezeigt wird, getrennt werden kann. Sie können mehr über die Theorie im obigen Artikel lesen. Ich werde die Komponenten in meiner Implementierung beschreiben:

  • Modell ist eine Datenstruktur, die zwischen View und Presenter übertragen wird und Daten sowohl zur Anzeige als auch zur Ausführung von Logik enthält. In meinem Fall ist das Modell Einstellungen. Wenn das Projekt gestartet wird, werden die Einstellungen in MainFormView geladen. Wenn der Ladevorgang beginnt, überprüft MainFormView die Einstellungen und übergibt sie an Presenter, sodass Presenter die Logik auf seiner Seite ausführt.
  • Ansicht ist eine Form, in der dem Benutzer Daten angezeigt werden. In meinem Fall handelt es sich um Daten aus dem Einstellungsmodell, und View bietet auch Ereignisse, sodass Presenter View mit BL verknüpft.

MainFormView implementiert eine gemeinsame IView-Schnittstelle, die allen View gemeinsam ist

    public interface IView
    {
        void Show();

        void Close();
    }

sowie eine private IMainFormView-Oberfläche, die nur für diese Ansicht spezifisch ist. Am Anfang habe ich darüber nachgedacht, es aufzugeben. Wenn Sie Presenter jedoch direkt mit dem Formular verknüpfen, steht bei der Arbeit mit einer solchen Ansicht der gesamte Satz formularspezifischer Methoden zur Verfügung, was nicht praktisch ist.

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

Eine weitere MVP-Neuerung besteht darin, dass die Show-Methode im Formular ersetzt wurde und der ApplicationContext über den Konstruktor an das Formular übergeben wird, sodass beim Wechsel von Formular zu Formular und Schließen das Hauptformular neu zugewiesen wird.

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

- Presenter ist eine Klasse, die View, Services und Business Logic (BL) in sich zusammenfasst und mit deren Hilfe die Interaktion zwischen View und Services organisiert wird. BL wird hauptsächlich in View-Ereignishandlern implementiert. Im Gegensatz zum zuvor verwendeten CodeBehind werden in MVP-Ereignishandlern, die BL ausführen, in Presenter angezeigt, und der Einfachheit halber werden Ereignisse in View in Form einer Aktion ohne Argumente angezeigt. Handler erhalten alle für die Ausführung erforderlichen Daten über das Modell, das aus dem Formular über die öffentliche Methode abgerufen wurde.

Presenter enthält die Run-Methode, die vom ApplicationController aufgerufen wird und das Formular startet:

    public interface IPresenter
    {
        void Run();
    }

ApplicationController - ein zentraler Kontroll- und Ausführungspunkt für die gesamte Anwendung. Verkapselt die gesamte Logik in sich: IoC, Präsentatoren, Ansicht, Dienste.

Die Verwaltung erfolgt über die Run-Methode, die den entsprechenden Presenter aufruft. Alle Präsentatoren sind über den ApplicationController miteinander verbunden, den der Präsentator im Konstruktor empfängt. Somit kann Presenter einen anderen Presenter aufrufen, indem er die Run-Methode aufruft, die intern den IoC-Container aufruft, um den gewünschten Presenter abzurufen und zu starten.

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

Der IoC-Container ist ein Aggregator aller in der Anwendungslogik verwendeten "Abhängigkeiten". Es beinhaltet:

  • Konstruktoren anzeigen
  • Konstrukteure Moderatoren
  • Dienstinstanzen
  • Anwendungskontext
  • ApplicationController

Alle Abhängigkeiten werden beim Start zum Container hinzugefügt. Dies ist in der Datei Program.cs zu sehen:

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

Für IoC habe ich die Lightinject-Komponente verwendet, die vor der Verwendung über NPM installiert werden muss.

Somit kann der Container sowohl Objektkonstruktoren als auch die Objekte selbst enthalten, wie dies bei Einstellungen und VKGroupHelperWorker (VK-API-Client) der Fall ist und die Menge aller verwendeten Anwendungsressourcen bildet. Ein nützliches Merkmal des Containers ist, dass alle diese eingebetteten Ressourcenklassen über die Konstruktorargumente abgerufen werden können. Beispiel:
ApplicationController, IMainFormView, VKGroupHelperWorker - zuvor implementierte Abhängigkeiten, die entweder Objektkonstruktoren oder Instanzen sein können. Wenn eine Instanz implementiert wurde, arbeiten alle generierten Objekte mit derselben Instanz, sodass Sie das Singleton-Muster entfernen können, wenn es verwendet wurde.

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

Durch die Implementierung von MVP konnte ich:

  • Singleton, das ich früher mit Anwendungseinstellungen verwendet habe, teilweise loswerden;
  • Trennen Sie BL von View, wodurch die Codetrennung (SRP) verbessert wird.
  • einen Ansatz zu entwickeln, um die Anwendung weiter zu erweitern, ohne die Ansicht zu überladen.

Weitere Informationen zu den durchgeführten Aktionen finden Sie im Projekt-Repository .

All Articles