Model - View - And What ؟
ماهو الـ Pattern المناسب لبناء تطبيقك ؟ مقارنة شاملة بين أشهر الـ Patterns ونصائح تساعد في إختيارك.
بسم الله الرحمن الرحيم
رمضان مبارك
يعتمد الكثير من المبرمجين ومطوري التطبيقات على عدة Design Architectural Patterns لبناء التطبيق الخاص بهم. هذه الـ Patterns ظهرت في الآونة الأخيرة وخصوصاً في مجال تطوير التطبيقات مثل الـ MVP MVC MVVM MVI وغيرها الكثير. وبدأ المطورون البحث عن الطريقة الأنسب لبناء تطبيقهم. لست هنا بصدد الحديث عن كل Pattern ولكن سنقوم بعمل مقارنة بسيطة بين أشهر الطرق المتبعة في تطوير التطبيقات وخصوصاً على نظام الأندرويد. هذه المقارنة تهدف لمعرفة أوجه التشابه والإختلاف بين هذه الأساليب بالإضافة إلى ذكر أهم المميزات والعيوب لكل أسلوب.
لكن السؤال هو ما الهدف والفائدة التي قد تعود على المطور عند إستخدامه Clean Architecture لبناء تطبيقه. لماذا لا نسهل الأمور على أنفسنا ونقوم بإضافة كل شي داخل كلاس واحد وهو ( في حالة الأندرويد ) كلاس الـ Activity أو الـ Fragment ؟
- فهم وتتبع الكود لباقي المطورين سيكون أسهل بكثير.
- القدرة على إختبار التطبيق بسلاسة أكبر ومعرفة موقع الخطأ بشكل أسرع.
- إعادة إستخدام بعض الأجزاء من التطبيق في تطبيقات أخرى تمتلك مميزات شبيهة بالتطبيق الذي تعمل عليه حالياً ( نقطة مهمة جداً ).
- جودة التطبيق ( Quality ) سترتفع تلقائياً.
تطوير تطبيقك بمثل هذه الأساليب له فوائد كثيرة لا حصر لها. ولكن .. السؤال الأهم هو .. ماهو الـ Pattern المناسب لي ؟ هل هو الـ MVP, MVC, MVVM, MVI ؟ أم غيرها من أساليب الـ 3 Tier Architecture .
أولاً وقبل الإجابة عن هذا السؤال، نحتاج لمعرفة الطرق الرئيسية التي تتشعب منها هذه الأساليب هذه الطرق تسمى بشكل عام ( Packaging Approaches ).
الطريقة الأولى : Feature Level
بمعنى أنه سيقوم المطور بجمع كل ما يتعلق بـ Feature معينة داخل باكج واحد. فـ على سبيل المثال، في الاندرويد سنفرض بأن كل Activity أو Fragment تقدم Feature واحدة معينة. لذا سنقوم بجمع كل ما يتعلق بهذه الـ Activity من كلاسات و Interfaces ونضعها في باكج واحد. أي أنه عند تطبيق واحدة من الـ Architectural Approaches التي بالأعلى سيكون هناك Model – View – Presenter في حالة الـ MVP لكل Activity
مميزات هذه الطريقة :
- عدم الإعتماد على أي باكج آخر. جميع متطلبات هذه الـ Activity ستتواجد معها في نفس الباكج. ( تساعد بشكل كبير إذا كان هناك أكثر من شخص يعملون على نفس التطبيق )
- إختبار هذه الـ Feature سهل إذ يمكنك إختبارها دون الحاجة لانتظار باقي الـ Features.
- إكتشاف الأخطاء وتعديلها سهل كون الباكجات معزولة تماماً عن بعضها البعض.
عيوب هذه الطريقة :
- الـ Reusability جداً منخفضة كون مجموعة من الـ Activities قد تتشارك في نفس الـ Model. ولكن ستجد نفسك مرغماً على بناء 2 Models لكل Activity. لذلك الـ Redundancy ستكون تلقائياً عالية.
- كثرة الـ Links بين الباكجات تضعف من جودة التطبيق، كون زيادة هذه الروابط يزيد من تعقيد التطبيق وخصوصاً عند صيانته أو تعديله.
الطريقة الثانية : Tier Level
بمعنى أنه سيقوم المطور بجمع كل ما يتعلق بـ Layer الـ View مثلا ويضعه في مكان واحد. وينطبق نفس المفهوم على Model و Presenter عند تطبيق MVP على سبيل المثال. أي أن التطبيق سيملك فقط 3 Layers.
مميزات هذه الطريقة :
- الـ Reusability جداً عالية كون الكلاسات المطلوبة من عدة Activities ستنشأ مرة واحدة فقط وتربط فيما بعض بمن يحتاجها.
- الروابط بين الـ Layers ستكون جداً قليلة وهذا يجعل الـ control flow داخل التطبيق واضح أكثر وقد يسهل من إيجاد الأخطاء ولكن ليس بسهولة الـ Feature level.
عيوب هذه الطريقة :
- سيكون من الصعب جداً عمل إختبار للـ Mid layer في غياب الـ Top and bottom layers.
- الروابط بين الـ Activities قد لا تكون واضحة. تغييرك لجزء بسيط من الكود قد يعطل كود آخر في مكان آخر يعتمد عليه.
نلاحظ مما سبق، عدم وجود طريقة مثالية يمكننا إتباعها دائماً. يجب على المطور تقييم التطبيق وفريق العمل والنظر في الخيار الأفضل.
هذه الطريقتين هي الطريقتين الأساسيتين المتبعة عند أغلب المطورين حتى عند تطبيقهم Architectures مختلفة. بعد شرح الطرق العامة سنتطرق للحديث بشكل أكبر عن بعض الـ Design Architectural Patterns.
نلاحظ بأن أغلب هذه الطرق تتشابه في أنها تملك Model & View.
الـ View: عبارة عن Contract Interface يسهل على الـ Mid layer التواصل مع الـ Activities أو الـ Fragment. الهدف الرئيسي من الـ Activity أو الـ Fragment هو عرض البيانات ومعرفة ما إذا قام المستخدم بعمل Action معين. أما إضافة كل شي في الـ Activity class فهذا خطأ. ليس من مهمة الـ Activity تخزين المعلومات المراد عرضها داخل الـ Activity. كما أنه ليس من مهمة الـ Activity القيام بـ Logic معين عند تنفيذ أمر معين ( مثلاً عند الضغط على الزر X قم بكتابة Hello وعند الضغط على الزر Y قم بكتابة Hi ) هذه الأمور لا تهم الـ View Layer. ما يهمها هو معرفة ما إذا قام المستخدم بضغط الزر X أو الزر Y. ثم تمرير هذا الـ Action إلى Layer أخرى للقيام بالأمور المنطقية.
الـ Model : عبارة عن Data Center. وظيفته الأساسية تخزين الـ Data وجلبها سواء عن طريق إستخدام API calls أو من داخل الجهاز نفسه. بإمكاننا إختصار دوره بأنه مسؤول عن كل ما يخص الـ Data.
يتبقى لنا الـ Business Logic وأين سيوضع. هننا تتباين الطرق وتختلف من حيث المكان الذي يوضع فيه الـ Logic. في كل Pattern سنقوم بوضع الـ Logic في Layer مختلفة.
سنقوم بالتركيز على أكثر طريقتين شائعة حالياً عند تطوير تطبيقات الأندرويد وهي MVP & MVVM .
MVP:
تقوم الـ Mid layer بربط الـ View مع الـ Model لتصبح هي صلة الوصل بينهما. بعكس الـ MVC والتي تسمح للـ View بالتواصل بشكل أو بآخر مع الـ Model. تسمى الـ Mid layer بإسم Presenter ويتم وضع كامل الـ Business Logic داخلها. تتواصل الـ Layers مع بعضها البعض من خلال مجموعة من الـ Interfaces حتى تسهل عملية التواصل. والسبب يكمن في أن الـ Presenter غير مهتم في كامل خصائص الـ Activity هو فقط يحتاج لإيصال مجموعة من النتائج إلى الـ View بناء على الـ Logic المنفذ. إيصال هذه النتائج يتم باستخدام بعض الـ Pre-defined methods داخل Interface معين.
في بعض الحالات عندما يكون الـ Logic معقد نوعاً ما. يتم عمل 2 presenters. الهدف منهما هو تقسيم الـ Logic على جزئين حتى يسهل إختبار كل جزء على حدى وحتى لا يصبح الكود داخل الـ Presenter طويل يصعب تعديله فيما بعد. ( هذه الطريقة غير متبعة بكثرة كونها أصعب ولكنها أفضل، ينصح بإستخدامها عندما يتغير تماماً مسار التطبيق بناء على حدوث Action معين أو عدم حدوثه)
مميزات MVP:
- سهولة الـ Implementation إلى حد كبير.
- كون الـ Model معزول تماماً عن الـ Logic فهذا يساهم في إعادة إستخدامه (Reusability) في أكثر من Presenter يستخدم نفس الـ Dataset.
عيوب MVP:
- High Coupling بين الـ View وبين الـ Presenter إذ يجب على كل Module معرفة الآخر.
- يجب عمل Handle للـ Lifecycle change يدوياً داخل الـ Presenter حتى يتم تفادي وقوع أي مشاكل متعلقة بالـ Lifecycle. ( تحدث هذه المشاكل بكثرة عندما يكون لديك أكثر من Call في الـ Activity. عند تأخر واحد من الـ Calls داخل الـ Activity والإنتقال إلى Activity أخرى دون إيقاف باقي الـ Calls، هذا سيجبر التطبيق على التوقف كون البيانات عند وصولها وجدت Context مختلف )
MVVM:
تقوم الـ Mid layer بوظيفة هامة جداً وهي حفظ الـ Data المعروضة داخل الـ View في مكان مختلف حتى لا تتأثر بحدوث أي تغيير داخل الـ View. يعتمد هذا الـ Approach بشكل كبير على مبدأ الـ Observer Design Pattern إذ تقوم الـ ViewModel (Mid layer) بجلب الـ Data من الـ Model ومن ثم عرضها للجميع. هنا يأتي دور الـ Views المهتمة بهذه الـ Data حيث تقوم بعمل Observe لهذه الـ Data لتتمكن من عرضها داخل الـ View الخاص بها. ولكن السؤال هنا .. أين نضع الـ Business Logic ؟
هنا تتباين الآراء حيث يرى بعض المطورين ضرورة وجود الـ Logic داخل كلاس الـ Activity نفسه كون الـ Logic خاص وينتمي فقط إلى هذه الـ Activity ولكن في هذه الحالة تم مخالفة الـ Single Responsibility Principle حيث تم إسناد أكثر من مهمة للـ Activity class. ماذا عن الـ Model ؟ وضع الـ Logic داخل الـ Model أو حتى الـ ViewModel غير منطقي كون الـ Modules لن تصبح قادرة على خدمة أكثر من View. وبالتالي الـ Reusability لهذه الـ Modules ستنخفض بشكل كبير. لذلك يبدو بأن وضع الـ Logic داخل الـ Activity class هو أفضل الخيارات السيئة.
مميزات MVVM:
- Loose coupling بين الـ View والـ ViewModel بحيث الـ View تربط نفسها بالـ ViewModel ولا حاجة لحدوث العكس.
- عدم إستخدام الكثير من الـ Interfaces.
- وجود مكتبات مدعومة من Android تسهل من تطبيق هذا الـ Architecture. ( LiveData – ViewModel ) يمكنك زيارة موضوعي السابق لمعرفة المزيد من المعلومات. هنا
عيوب MVVM:
- وجود الـ Logic داخل أي Layer يعتبر غير جيد كونه لا ينتمي لأي Layer.
يعتقد الكثير من المطورين بأن الحل الأنسب هو إنشاء Presenter لوضع كامل الـ Logic داخله بالإضافة إلى إستخدام MVVM للإستفادة من المكتبات المقدمة من الأندرويد. مع الأخذ بعين الإعتبار ضرورة عمل Handle لتغيرات الـ Context داخل هذا الـ Presenter.
ولكن الإقدام على مثل هذا الأمر قد يسبب المزيد من التعقيد وخصوصاً عند رؤية Presenter و ViewModel في نفس الوقت.
الحل يكمن في فصل الـ ViewModels تماماً عن الـ Activities ووضعها داخل Module خاص فيها. في هذه الحالة سنقوم بإسناد مهمة الـ Observing إلى الـ Activity ومن ثم إعلام الـ Presenter في حال وجود أي تغيير في الـ Dataset. ليصبح شكل التطبيق النهائي MVP يتخلله وجود بعض الـ ViewModels المهمة.
نلاحظ من الصورة الآتي :
1- تقوم الـ Activity بمراقبة ( Observe ) الـ ViewModels المطلوبة وعند تغير الـ Data ستقوم الـ Activity بإخبار الـ Presenter بهذا التغيير. ثم سيقوم الـ Presenter بأخذ البيانات من الـ ViewModels.
2- تتواصل الـ Presenters مع الـ Model في حالة واحدة فقط وهي حالة إضافة الـ Records أما في حالة البحث عنها فسيتم ذلك عن طريق الـ ViewModels كما أسلفنا.
3- يفضل بناء Class Diagram شامل لكل التطبيق قبل إنشاء الـ Models و ViewModels ومعرفة الكلاسات المطلوبة كي نتجنب بناء كلاسات مكررة.
المصادر:
Meduim - droidcon - Clean architecture by Robert Martin
إلى هنا نكون قد إنتهينا من الموضوع .. نلقاكم في مواضيع أندرويدية أخرى بإذن الله :)
التعليقات (5)
موضوع شيق ومقالة رائعة
يعطيك العافيه طرح رهيب ياريت تضيف رابط المصدر بيكون افضل 🤩
موضوع مميز وسلس بارك الله فيك
شرحح ممتاز ولغة سهلة بسيطة وترتيب للمعملومات بشكل أكثر من رائع أحسنت
مجهود مشكور ❤️
عرض المزيد.. جديد مقالاتي
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !