Implémentation de MVP basée sur ApplicationController et IoC dans une application WinForms

Bonne après-midi!

Dans cet article, je vais parler de la façon dont j'ai implémenté le modèle MVP dans mon application Windows Forms et décrire les situations pratiques et les fonctionnalités de l'utilisation d'IoC et d'ApplicationController. Passer de codebehind à MVP m'a permis:

  • améliorer la lisibilité grâce à une meilleure séparation du code (SRP) - séparer BL de View;
  • développer une méthodologie pour étendre davantage la fonctionnalité de l'application;
  • se débarrasser de singleton, que je travaillais avec les paramètres d'application.

À propos de l'application


Une application pour aider à gérer les groupes VC. Vous permet de remplir un groupe avec des messages en attente. La principale fonctionnalité pour le moment est le chargement de publications en attente dans le groupe VK avec des photos ou des vidéos, des hashtags, des sondages, la géolocalisation et la possibilité de configurer l'heure de publication et le nombre de publications par jour. Pour le moment, la demande a un seul formulaire.

Avec la croissance de sa fonctionnalité, beaucoup de code s'est accumulé, ce qui a commencé à confondre le fait que le formulaire est devenu très chargé et contenait tout en lui-même. Lors de la planification de la poursuite du développement du projet, je ne pouvais pas imaginer ce qui pouvait être poursuivi dans le même esprit, et maintenant le moment est venu où la tâche principale n'était pas d'affiner la fonctionnalité, mais de refactoriser. Et j'ai commencé à chercher une solution qui permettrait d'optimiser et de diviser le code et d'améliorer généralement l'architecture de l'application afin qu'il soit plus agréable de travailler avec.

La source

Solution de refactoring


La solution était d'implémenter le modèle MVP. Comme base, j'ai pris l'article MVP Implementation Features for Windows Forms .

L'article analyse un exemple étendu d'une application simple avec 3 formes: 2 principales et 1ère modale. L'article discute d'une approche très avancée:

  • en plus d'ApplicationController et d'IoC, l'adaptateur y est également utilisé, ce qui permet d'utiliser différents IoC;
  • 3 types de formulaires: avec paramètres, sans paramètres et modal;
  • Le principe DIP est largement appliqué.

Dans mon projet, j'utilise un seul formulaire sans arguments, abandonné l'adaptateur (suivant le principe YAGNI), car IoC Lightinject me suffit et dans une moindre mesure j'utilise DIP pour simplifier le projet.

Implémentation MVP


MVP (Model-View-Presenter) est un modèle de conception conçu pour faciliter la séparation de la logique métier de la façon dont elle est affichée. Vous pouvez en savoir plus sur la théorie dans l'article ci-dessus. Je décrirai les composants de mon implémentation:

  • Le modèle est une structure de données transférée entre View et Presenter et contient des données à la fois pour l'affichage et pour l'exécution de la logique. Dans mon cas, le modèle est Paramètres. Lorsque le projet démarre, les paramètres sont chargés dans MainFormView et lorsque le chargement démarre, MainFormView vérifie et transmet Settigns à Presenter afin que Presenter exécute la logique de son côté.
  • La vue est un formulaire dans lequel les données sont affichées pour l'utilisateur. Dans mon cas, il s'agit de données du modèle Paramètres, et View fournit également des événements afin que Presenter associe View à BL.

MainFormView implémente une interface IView commune à tous les View

    public interface IView
    {
        void Show();

        void Close();
    }

ainsi qu'une interface IMainFormView privée spécifique à cette vue uniquement. Au début, j'ai pensé à l'abandonner, mais si vous associez Presenter directement au formulaire, alors lorsque vous travaillez avec une telle vue, l'ensemble des méthodes spécifiques à Form sera disponible, ce qui n'est pas pratique.

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

Une autre innovation MVP est que la méthode Show a été remplacée dans le formulaire et que ApplicationContext est passé au formulaire via le constructeur, de sorte que lors du passage du formulaire au formulaire et à sa fermeture, le formulaire principal est réaffecté.

        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 est une classe qui encapsule la vue, les services et la logique métier (BL), à l'aide de laquelle elle organise l'interaction entre la vue et les services. BL est principalement implémenté dans les gestionnaires d'événements View. Contrairement au CodeBehind précédemment utilisé, dans les gestionnaires d'événements MVP exécutant BL sont affichés dans Presenter, et pour plus de simplicité, les événements dans View sont affichés sous forme d'Action sans arguments. Les gestionnaires reçoivent toutes les données nécessaires à l'exécution via le modèle obtenu à partir du formulaire via la méthode publique.

Presenter contient la méthode Run, qui est appelée par ApplicationController et qui lance le formulaire:

    public interface IPresenter
    {
        void Run();
    }

ApplicationController - un seul point de contrôle et d'exécution de l'application entière. Encapsule toute la logique en soi: IoC, Presenters, View, Services.

La gestion se fait via la méthode Run, qui appelle le Presenter correspondant. Tous les présentateurs sont connectés les uns aux autres via l'ApplicationController, que le présentateur reçoit dans le constructeur. Ainsi, Presenter peut appeler un autre Presenter en appelant la méthode Run, qui appelle en interne le conteneur IoC pour obtenir le Presenter souhaité et le démarrer.

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

Le conteneur IoC est un agrégateur de toutes les "dépendances" utilisées dans la logique d'application. Il contient:

  • Voir les constructeurs
  • Constructeurs Présentateurs
  • instances de service
  • contexte d'application
  • ApplicationController

Toutes les dépendances sont ajoutées au conteneur lors du démarrage, cela peut être vu dans le fichier 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>();
        }

Pour IoC, j'ai utilisé le composant Lightinject, qui doit être installé via NPM avant utilisation.

Ainsi, le conteneur peut contenir à la fois des constructeurs d'objets et les objets eux-mêmes, comme cela se fait avec Settings et VKGroupHelperWorker (client API VK), formant l'ensemble de toutes les ressources d'application utilisées. Une caractéristique utile du conteneur est que toutes ces ressources incorporées, les classes peuvent être obtenues via les arguments du constructeur. Par exemple,
ApplicationController, IMainFormView, VKGroupHelperWorker - dépendances précédemment implémentées, qui peuvent être des constructeurs d'objets ou des instances. Si une instance a été implémentée, tous les objets générés fonctionneront avec la même instance, ce qui vous permet de vous débarrasser du modèle singleton, s'il est utilisé.

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

La mise en œuvre de MVP m'a permis de:

  • se débarrasser partiellement de Singleton, que je travaillais avec les paramètres d'application;
  • Séparez BL de View, améliorant ainsi la séparation de code (SRP)
  • pour développer une approche permettant d'étendre davantage l'application sans encombrer la vue.

Vous trouverez plus d'informations sur ce qui a été fait dans le référentiel du projet .

All Articles