قاده التفكير
تنفيذ مبادئ SOLID في تطوير Android
إن كتابة البرامج هي فعل إبداعي، وتطوير Android ليس استثناءً. الأمر لا يتعلق فقط بجعل شيء ما يعمل. بل يتعلق بتصميم تطبيقات يمكنها النمو والتكيف والبقاء قابلة للإدارة بمرور الوقت.
بصفتي مطورًا لنظام Android واجه عددًا لا يحصى من التحديات المعمارية، فقد اكتشفت أن الالتزام بمبادئ SOLID يمكن أن يحول حتى أكثر قواعد التعليمات البرمجية تعقيدًا إلى أنظمة نظيفة. هذه ليست مبادئ مجردة، بل طرق موجهة نحو النتائج وقابلة للتكرار لكتابة تعليمات برمجية قوية وقابلة للتطوير وقابلة للصيانة.
ستقدم هذه المقالة نظرة ثاقبة حول كيفية تطبيق مبادئ SOLID على تطوير Android من خلال أمثلة واقعية وتقنيات عملية وخبرة من فريق Meta WhatsApp.
فهم مبادئ SOLID
مبادئ SOLID، التي اقترحها روبرت سي مارتن، هي خمسة مبادئ تصميم للبرمجة الموجهة للكائنات والتي تضمن بنية برمجية نظيفة وفعالة.
- مبدأ المسؤولية الفردية (SRP): يجب أن يكون لدى الفصل سبب واحد فقط للتغيير.
- المبدأ المفتوح/المغلق (OCP): يجب أن تكون الكيانات البرمجية مفتوحة للتوسع ولكن مغلقة للتعديل.
- مبدأ استبدال ليسكوف (LSP): يجب أن تكون الأنواع الفرعية قابلة للاستبدال بأنواعها الأساسية.
- مبدأ فصل الواجهة (ISP): يجب أن تكون الواجهات خاصة بالعميل ولا تجبر على تنفيذ طرق غير مستخدمة.
- مبدأ عكس التبعية (DIP): ينبغي أن تعتمد الوحدات النمطية عالية المستوى على التجريدات، وليس على الوحدات النمطية منخفضة المستوى.
من خلال دمج هذه المبادئ في تطوير Android، يمكننا إنشاء تطبيقات أسهل للتطوير والاختبار والصيانة.
مبدأ المسؤولية الفردية (SRP): تبسيط المسؤوليات
مبدأ المسؤولية الفردية هو أساس كتابة التعليمات البرمجية القابلة للصيانة. وينص على أن كل فئة يجب أن يكون لها اهتمام واحد تتحمل مسؤوليته. ومن الأنماط المضادة الشائعة اعتبار الأنشطة أو الأجزاء بمثابة "فئات إلهية" تتعامل مع المسؤوليات بدءًا من عرض واجهة المستخدم، ثم جلب البيانات، ومعالجة الأخطاء، وما إلى ذلك. وهذا النهج يجعل الاختبار والصيانة كابوسًا.
باستخدام SRP، يمكنك فصل الاهتمامات المختلفة إلى مكونات مختلفة: على سبيل المثال، في تطبيق للأخبار، قم بإنشاء أو قراءة الأخبار.
class NewsRepository {
fun fetchNews(): List {
// Handles data fetching logic
}
}
class NewsViewModel(private val newsRepository: NewsRepository) {
fun loadNews(): LiveData<List> {
// Manages UI state and data flow
}
}
class NewsActivity : AppCompatActivity() {
// Handles only UI rendering
}
كل فئة لديها مسؤولية واحدة فقط؛ وبالتالي، من السهل اختبارها وتعديلها دون التعرض لآثار جانبية.
في تطوير Android الحديث، يتم تنفيذ SRP في الغالب جنبًا إلى جنب مع البنية الموصى بها باستخدام Jetpack. على سبيل المثال، قد يوجد المنطق المتعلق بمنطق معالجة البيانات داخل ViewModel، بينما يجب أن تهتم الأنشطة أو الشظايا فقط بواجهة المستخدم والتفاعلات. قد يتم تفويض جلب البيانات إلى مستودع منفصل، إما من قواعد البيانات المحلية مثل Room أو طبقات الشبكة مثل Retrofit. يقلل هذا من خطر تضخم فئات واجهة المستخدم، حيث يحصل كل مكون على مسؤولية واحدة فقط. في الوقت نفسه، سيكون اختبار ودعم الكود الخاص بك أسهل كثيرًا.
مبدأ الانفتاح/الإغلاق (OCP): التصميم للتوسع
ينص مبدأ الفتح/الإغلاق على أنه يجب فتح الفئة للتوسع ولكن ليس للتعديل. وهو أكثر منطقية لتطبيقات Android لأنها تقوم بالتحديث باستمرار وإضافة ميزات جديدة.
أفضل مثال على كيفية استخدام مبدأ OCP في تطبيقات Android هو الواجهات والفئات المجردة. على سبيل المثال:
interface PaymentMethod {
fun processPayment(amount: Double)
}
class CreditCardPayment : PaymentMethod {
override fun processPayment(amount: Double) {
// Implementation for credit card payments
}
}
class PayPalPayment : PaymentMethod {
override fun processPayment(amount: Double) {
// Implementation for PayPal payments
}
}
لا تتطلب إضافة طرق دفع جديدة إجراء تغييرات على الفئات الحالية؛ بل تتطلب إنشاء فئات جديدة. وهنا يصبح النظام مرنًا ويمكن توسيعه.
في التطبيقات التي تم إنشاؤها لأجهزة Android، يكون مبدأ الفتح/الإغلاق مفيدًا جدًا عندما يتعلق الأمر بتبديل الميزات والتكوينات التي يتم إجراؤها ديناميكيًا. على سبيل المثال، في حالة احتواء تطبيقك على واجهة AnalyticsTracker الأساسية التي تبلغ عن الأحداث إلى خدمات تحليلات مختلفة، مثل Firebase وMixpanel ومتتبعات داخلية مخصصة، يمكن إضافة كل خدمة جديدة كفئة منفصلة دون إجراء تغييرات على الكود الحالي. وهذا يحافظ على وحدة التحليلات الخاصة بك مفتوحة للتمديد - يمكنك إضافة متتبعات جديدة - ولكن مغلقة للتعديل: لا تعيد كتابة الفئات الحالية في كل مرة تضيف فيها خدمة جديدة.
مبدأ استبدال ليزكوف (LSP): ضمان التبادلية
تنص مبادئ استبدال Liskov على أن الفئات الفرعية يجب أن تكون قابلة للاستبدال بفئاتها الأساسية، ويجب ألا يتغير سلوك التطبيق. في Android، يعد هذا المبدأ أساسيًا لتصميم مكونات قابلة لإعادة الاستخدام وقابلة للتنبؤ.
على سبيل المثال، تطبيق الرسم:
abstract class Shape {
abstract fun calculateArea(): Double
}
class Rectangle(private val width: Double, private val height: Double) : Shape() {
override fun calculateArea() = width * height
}
class Circle(private val radius: Double) : Shape() {
override fun calculateArea() = Math.PI * radius * radius
}
يبلغ قطر كلاً من مستطيل و دائرة يمكن استبدالها بأي نظام آخر بشكل متبادل دون فشل النظام، مما يعني أن النظام مرن ويتبع LSP.
ضع في اعتبارك نظام Android RecyclerView.Adapter الفئات الفرعية. كل فئة فرعية من المحول تمتد من مُحوِّل RecyclerView ويتجاوز الوظائف الأساسية مثل onCreateViewHolder, onBindViewHolderو getItemCount. عرض إعادة التدوير يمكنك استخدام أي فئة فرعية بالتبادل طالما تم تنفيذ هذه الأساليب بشكل صحيح ولا تتسبب في تعطيل وظائف تطبيقك. هنا، يتم صيانة LSP، ويمكن أن يكون RecyclerView الخاص بك مرنًا لاستبدال أي فئة فرعية للمحول حسب الرغبة.
مبدأ فصل الواجهة (ISP): واجهات بسيطة ومحددة
في التطبيقات الأكبر حجمًا، من الشائع تعريف واجهات ذات مسؤولية كبيرة للغاية، وخاصة فيما يتعلق بالشبكات أو تخزين البيانات. بدلاً من ذلك، قم بتقسيمها إلى واجهات أصغر وأكثر استهدافًا. على سبيل المثال، يجب أن تكون واجهة ApiAuth المسؤولة عن نقاط نهاية مصادقة المستخدم مختلفة عن واجهة ApiPosts المسؤولة عن منشورات المدونة أو نقاط نهاية موجز الوسائط الاجتماعية. سيمنع هذا الفصل العملاء الذين يحتاجون فقط إلى الأساليب المتعلقة بالمنشور من الاعتماد على مكالمات المصادقة وتنفيذها، وبالتالي الحفاظ على الكود الخاص بك، بالإضافة إلى تغطية الاختبار، أكثر رشاقة.
مبدأ فصل الواجهة يعني أنه بدلاً من استخدام واجهات كبيرة، يجب استخدام عدة واجهات أصغر وأكثر تركيزًا. يمنع هذا المبدأ المواقف التي تنفذ فيها الفئات طرقًا غير ضرورية.
على سبيل المثال، بدلاً من استخدام واجهة كبيرة تمثل تصرفات المستخدمين، ضع في اعتبارك كود Kotlin:
interface Authentication {
fun login()
fun logout()
}
interface ProfileManagement {
fun updateProfile()
fun deleteAccount()
}
يمكن للفئات التي تنفذ هذه الواجهات التركيز فقط على الوظائف التي تتطلبها، وبالتالي تنظيف الكود وجعله أكثر قابلية للصيانة.
مبدأ عكس التبعية (DIP): تجريد التبعيات
يعمل مبدأ عكس التبعية على تعزيز الفصل من خلال ضمان اعتماد الوحدات النمطية عالية المستوى على التجريدات بدلاً من التنفيذات الملموسة. يتماشى هذا المبدأ تمامًا مع ممارسات التطوير الحديثة لنظام Android، وخاصة مع أطر حقن التبعية مثل Dagger وHilt.
فمثلا:
class UserRepository @Inject constructor(private val apiService: ApiService) {
fun fetchUserData() {
// Fetches user data from an abstraction
}
}
هنا، مستودع المستخدم يعتمد على التجريد خدمة APIمما يجعلها مرنة وقابلة للاختبار. يسمح لنا هذا النهج باستبدال التنفيذ، مثل استخدام خدمة وهمية أثناء الاختبار.
تسهل الأطر مثل Hilt وDagger وKoin حقن التبعيات من خلال توفير طريقة لتزويد التبعيات لمكونات Android، مما يلغي الحاجة إلى إنشائها بشكل مباشر. في المستودع، على سبيل المثال، بدلاً من إنشاء مثيل لتطبيق Retrofit، ستحقن تجريدًا - على سبيل المثال، واجهة ApiService. بهذه الطريقة، يمكنك بسهولة تبديل تنفيذ الشبكة - على سبيل المثال، خدمة وهمية في الذاكرة للاختبار المحلي - ولن تحتاج إلى تغيير أي شيء في كود المستودع الخاص بك. في التطبيقات الواقعية، يمكنك أن تجد أن الفئات مُعلقة بـ @Inject أو @Provides لتوفير هذه التجريدات، وبالتالي جعل تطبيقك معياريًا وسهل الاختبار.
الفوائد العملية لمبادئ SOLID
يؤدي اعتماد مبادئ SOLID في تطوير Android إلى فوائد ملموسة:
- تحسين إمكانية الاختبار: تُسهّل الفئات والواجهات المُركّزة كتابة اختبارات الوحدة.
- تحسين الصيانة: يؤدي الفصل الواضح بين الاهتمامات إلى تبسيط عملية تصحيح الأخطاء والتحديثات.
- التدرجية: تتيح التصميمات المعيارية إمكانية إضافة ميزات سلسة.
- التعاون: يُسهّل الكود المُهيكل جيدًا العمل الجماعي ويقلل من وقت الانضمام للمطورين الجدد.
- تحسين الأداء: تعمل الهندسة المعمارية الفعالة والمرنة على تقليل المعالجة غير الضرورية واستخدام الذاكرة.
تطبيقات العالم الحقيقي
في التطبيقات الغنية بالميزات، مثل تطبيقات التجارة الإلكترونية أو الشبكات الاجتماعية، يمكن أن يؤدي تطبيق مبادئ SOLID إلى تقليل مخاطر التراجع بشكل كبير في كل مرة تتم فيها إضافة ميزة أو خدمة جديدة. على سبيل المثال، إذا كان المتطلب الجديد يتطلب تدفق شراء داخل التطبيق، فيمكنك تقديم وحدة منفصلة ستنفذ الواجهات المطلوبة (الدفع والتحليلات) دون المساس بالوحدات النمطية الموجودة. يتيح هذا النوع من النهج المعياري، الذي يعتمد على SOLID، لتطبيق Android الخاص بك التكيف بسرعة مع متطلبات السوق ويمنع قاعدة التعليمات البرمجية من التحول إلى معكرونة بمرور الوقت.
عند العمل على مشروع كبير يتطلب تعاون العديد من المطورين، يوصى بشدة بالحفاظ على قاعدة بيانات معقدة بمبادئ SOLID. على سبيل المثال، ساعد فصل جلب البيانات ومنطق العمل ومعالجة واجهة المستخدم في وحدة الدردشة في تقليل فرصة حدوث انحدارات أثناء توسيع نطاق الكود بميزات جديدة. وبالمثل، كان تطبيق DIP أمرًا بالغ الأهمية لعمليات الشبكة المجردة، وبالتالي القدرة على التغيير دون أي انقطاع تقريبًا بين عملاء الشبكة.
خاتمة
إن مبادئ SOLID ليست مجرد دليل نظري، بل هي في الواقع فلسفة عملية لإنشاء برامج مرنة وقابلة للتكيف وقابلة للصيانة. وفي عالم تطوير Android سريع الحركة، حيث تتغير المتطلبات بنفس وتيرة تغير التكنولوجيا، فإن الالتزام بهذه المبادئ يوفر أرضية صلبة يمكن أن يرتكز عليها النجاح.
لا يتعلق الكود الجيد فقط بجعل شيء ما يعمل، بل يتعلق بإنشاء نظام يمكنه الاستمرار في العمل والنمو مع الاحتياجات المتطورة. من خلال تبني مبادئ SOLID، لن تكتب كودًا أفضل فحسب، بل ستنشئ أيضًا تطبيقات ممتعة للتطوير والتوسع والصيانة.












