تنفيذ MVP على أساس ApplicationController و IoC في تطبيق WinForms

طاب مسائك!

في هذه المقالة ، سأتحدث عن كيفية تنفيذ نمط MVP في تطبيق Windows Forms الخاص بي ووصف الحالات والميزات العملية لاستخدام IoC و ApplicationController. لقد سمح لي التحول من codebehind إلى MVP:

  • تحسين قابلية القراءة بسبب فصل الشفرة بشكل أفضل (SRP) - فصل BL عن طريقة العرض ؛
  • لتطوير منهجية لزيادة توسيع وظائف التطبيق ؛
  • تخلص من المفرد الذي اعتدت أن أعمله مع إعدادات التطبيق.

حول التطبيق


تطبيق للمساعدة في إدارة مجموعات VC. يتيح لك ملء مجموعة بالمشاركات المعلقة. تتمثل الوظيفة الرئيسية في الوقت الحالي في تحميل المشاركات المعلقة في مجموعة VK مع الصور أو مقاطع الفيديو وعلامات التصنيف والاستطلاعات والموقع الجغرافي والقدرة على تكوين وقت النشر وعدد المشاركات حسب اليوم. في الوقت الحالي ، يحتوي التطبيق على نموذج واحد.

مع نمو وظائفه ، تراكم الكثير من كودبايهن ، والتي بدأت في الخلط بين حقيقة أن النموذج أصبح محملاً للغاية واحتوى على كل شيء في حد ذاته. عند التخطيط لمزيد من تطوير المشروع ، لم أكن أتخيل ما يمكن أن يستمر أكثر بنفس الروح ، والآن جاءت اللحظة التي لم تكن المهمة الرئيسية فيها هي تحسين الوظيفة ، ولكن إعادة البناء. وبدأت في البحث عن حل من شأنه أن يساعد على تحسين الشفرة وتقسيمها وتحسين بنية التطبيق بشكل عام بحيث يكون العمل معها أكثر متعة.

مصدر

حل إعادة البيع


كان الحل تطبيق نمط MVP. كأساس ، أخذت المقالة ميزات تطبيق MVP لنماذج Windows .

المقالة تحلل مثالا موسعا لتطبيق بسيط مع 3 أشكال: 2 الرئيسي والوسيط الأول. تناقش المقالة نهجًا متقدمًا للغاية:

  • بالإضافة إلى ApplicationController و IoC ، يتم استخدام المحول أيضًا هناك ، مما يسمح باستخدام IoCs مختلفة ؛
  • 3 أنواع من الأشكال: مع معلمات ، بدون معلمات وشكل ؛
  • يتم تطبيق مبدأ DIP على نطاق واسع.

في مشروعي ، أستخدم نموذجًا واحدًا فقط بدون حجج ، تخلت عن المحول (وفقًا لمبدأ YAGNI) ، لأن IoC Lightinject يكفي بالنسبة لي وبدرجة أقل استخدم DIP لتبسيط المشروع.

تنفيذ MVP


MVP (Model-View-Presenter) هو نمط تصميم مصمم لراحة فصل منطق الأعمال عن طريقة عرضه. يمكنك قراءة المزيد عن النظرية في المقالة أعلاه. سأصف المكونات في التنفيذ:

  • النموذج هو بنية بيانات يتم نقلها بين العرض والمقدم ويحتوي على بيانات للعرض ولتنفيذ المنطق. في حالتي ، النموذج هو الإعدادات. عندما يبدأ المشروع ، يتم تحميل الإعدادات في MainFormView ، وعندما يبدأ التحميل ، يقوم MainFormView بفحص واجتياز تعيينات إلى مقدم العرض ، بحيث يقوم مقدم العرض بتشغيل المنطق من جانبه.
  • العرض هو نموذج يتم فيه عرض البيانات للمستخدم. في حالتي ، هذه بيانات من نموذج الإعدادات ، كما توفر طريقة العرض الأحداث بحيث يربط المقدم العرض بـ BL.

يطبق MainFormView واجهة IView مشتركة بين جميع طرق العرض

    public interface IView
    {
        void Show();

        void Close();
    }

بالإضافة إلى واجهة IMainFormView خاصة خاصة بهذا العرض فقط. في البداية ، فكرت في التخلي عنه ، ولكن إذا ربطت مقدم العرض مباشرة بالنموذج ، فعند العمل مع طريقة العرض هذه ، ستتوفر مجموعة كاملة من الأساليب الخاصة بالنموذج ، وهو أمر غير ملائم.

    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 بشكل أساسي في معالجات الأحداث. على عكس CodeBehind المستخدم سابقًا ، في معالجات أحداث MVP التي تقوم بتنفيذ BL يتم عرضها في مقدم العرض ، وللتبسيط ، يتم عرض الأحداث في طريقة العرض في شكل إجراء بدون وسيطات. تتلقى معالجات جميع البيانات اللازمة للتنفيذ من خلال النموذج الذي تم الحصول عليه من النموذج من خلال الطريقة العامة.

يحتوي المقدم على طريقة التشغيل ، التي يطلق عليها ApplicationController والتي تقوم بتشغيل النموذج:

    public interface IPresenter
    {
        void Run();
    }

ApplicationController - نقطة تحكم واحدة وتنفيذ كامل للتطبيق. يغلف كل المنطق في حد ذاته: IoC، Presenters، View، Services.

الإدارة عن طريق طريقة التشغيل ، والتي تستدعي المقدم المقابل. يتم توصيل جميع مقدمي العروض ببعضهم البعض من خلال ApplicationController ، الذي يستقبله مقدم العرض في المُنشئ. وبالتالي ، يمكن لمقدم العرض استدعاء مقدم آخر عن طريق استدعاء طريقة التشغيل ، التي تستدعي داخليًا حاوية 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 هي عبارة عن مجمع لجميع "التبعيات" المستخدمة في منطق التطبيق. أنه يحتوي على:

  • عرض المنشئين
  • مقدمو البناء
  • مثيلات الخدمة
  • سياق التطبيق
  • ApplicationController

تتم إضافة كافة التبعيات إلى الحاوية أثناء بدء التشغيل ، ويمكن رؤيتها في ملف 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 قبل الاستخدام.

وبالتالي ، يمكن أن تحتوي الحاوية على كل من مُنشئ الكائنات والكائنات نفسها ، كما هو الحال مع الإعدادات و 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 عن العرض ، وبالتالي تحسين فصل الكود (SRP)
  • لتطوير نهج لتوسيع التطبيق دون فوضى العرض.

يمكن العثور على مزيد من المعلومات حول ما تم القيام به في مستودع المشروع .

All Articles