SRP (Single Responsibility Principle) — أول مبدأ من مبادئ SOLID، وهو ينص على: يجب أن يكون لكل فئة أو وحدة سبب واحد فقط للتغيير. صاغ هذا المبدأ روبرت مارتين في كتاب Clean Architecture (2017) وأصبح أساس التصميم الوحداتي. وفقًا لهذا الكتاب، تطبيق SRP يقلل مباشرة اقتران المكونات ويلغي التغييرات المتتابعة عند تعديل الوظائف.
النقاط الرئيسية
SRP (Single Responsibility Principle) — مبدأ المسؤولية الواحدة، وهو ينص على: يجب أن يكون لكل فئة أو وحدة سبب واحد فقط للتغيير. هذا لا يعني أن الفئة يجب أن تقوم بعملية واحدة فقط. الأمر يتعلق بمجموعة من الإجراءات المترابطة التي تجمعها مسؤولية واحدة أمام فاعل واحد.
أعاد روبرت مارتين صياغة SRP بمصطلحات الفاعلين: يجب أن تتغير الفئة فقط بطلب طرف واحد مهتم أو مجموعة واحدة من الأشخاص. إذا كان فاعلان مختلفان يطلبان تغييرات في نفس الفئة، فإن المسؤولية مقسمة بشكل غير صحيح.
على سبيل المثال، فئة Employee التي تحسب الراتب (طلب قسم المحاسبة) وتنشئ التقارير (طلب الإدارة) تنتهك SRP. قد يؤثر تغيير قواعد الحساب على إنشاء التقارير والعكس.
يجب أن يكون للوحدة سبب واحد وحيد للتغيير. يتم تحديد سبب التغيير بواسطة فاعل — شخص أو نظام يبدأ المتطلب. إذا كانت متطلبات من فاعلين مختلفين تؤدي إلى تغيير وحدة واحدة، فإن الوحدة تنتهك SRP.
يجعل مفهوم الفاعل SRP أداة عملية للتحليل المعماري، وليس توصية مجردة. عند تصميم نظام، يكفي أن تسأل: «من سيطلب تغيير هذا الكود؟» — إذا كانت الإجابة تتضمن أكثر من طرف واحد مهتم، فإن المسؤولية يجب أن تقسم.
تتم تطبيق المسؤولية الواحدة من خلال تجميع الطرق التي تتغير لسبب واحد. تصبح الفئة نقطة تجمع للمنطق المترابط لا سكينًا سويسرية لكل الحالات. يبسط هذا فهم الكود: يرى المطور الفئة ويفهم فورًا غرضها.
يعتمد آلية عمل SRP على قاعدة محور التغيير الواحد. إذا كانت الوظيفة قابلة للتغيير لأسباب مستقلة، فينبغي استخراجها إلى فئات منفصلة. تبنى الصلات بين هذه الفئات من خلال التركيب أو التفويض.
يظهر انتهاك SRP في كائنات عملاقة تحتوي على عشرات الطرق التي تعمل مع بيانات مختلفة. يصعب اختبار هذه الفئات — فاختبار طريقة واحدة يتطلب إعداد البيئة لجميع الطرق الأخرى. قد يكسر تغيير مسؤولية واحدة أخرى، مما يجعل الكود هشًا.
في الممارسة، يساعد SRP المطورين في الإجابة على سؤال «أين يوجد هذا الكود؟». إذا كانت كل مسؤولية منفصلة في فئتها الخاصة، فإن العثور على الملف المناسب يستغرق ثوانٍ. في مشروع Android بمعمارية MVVM، يعني هذا أن UserViewModel يكون مسؤولًا فقط عن حالة شاشة المستخدم، وUserRepository عن جلب البيانات. المطور الذي يبحث عن منطق التخزين المؤقت يذهب إلى UserCacheRepository، وليس إلى ViewModel. هذا التنظيم يسرع انضمام أعضاء الفريق الجدد ويقلل عدد الأخطاء أثناء إعادة الهندسة.
يفرض التطوير المحمول متطلبات خاصة بالنسبة لوحداتية الكود. غالبًا ما تصبح Fragment في Android أو ViewController في iOS نقاط جذب للمنطق: معالجة النقرات، استدعاء API، تحليل الاستجابة، تحديث UI — كله في فئة واحدة. SRP يتطلب فصل هذه المسؤوليات.
في معمارية Android، SRP مدمج في توصيات Google بشأن Jetpack: ViewModel مسؤول عن حالة الشاشة، Repository عن البيانات، UseCase عن منطق الأعمال. لكل مكون سبب واحد للتغيير. في تطوير iOS، تتبع أنماط MVVM وCoordinator نفس المنطق.
يحقق الالتزام بـ SRP في المشاريع المحمولة فوائد قابلة للقياس: تقليل حجم الفئات بنسبة 40-60%، تقليص الوقت في مراجعات الكود وتقليل أخطاء الارتداد عند إضافة وظائف جديدة. الوحدات المعزولة أسهل في تغطيتها باختبارات الوحدة وإعادة استخدامها في شاشات أخرى.
تتطلب اختبارات الوحدة للفئات المتوافقة مع SRP عددًا أقل من كائنات المحاكاة وإعدادات أقل. إذا كان للفئة مسؤولية واحدة، فإن تبعياتها محدودة. يختبر الاختبار سلوكًا واحدًا، وليس مزيجًا من سيناريوهات غير مرتبطة.
وفقًا لتقرير Google Testing Blog (2023)، تظهر الفئات ذات المسؤولية الواحدة تغطية اختبارية أعلى بنسبة 35% مقارنة بالفئات التجميعية. يكتب المطورون اختبارات بشكل أكبر للوحدات الصغيرة المفهومة.
لننظر إلى فئة Android نموذجية تنتهك SRP — تقوم بتحميل البيانات وتحليل الاستجابة وتحديث UI. بعد إعادة الهندسة، كل مسؤولية منفصلة في مكونها الخاص.
// انتهاك SRP: فئة واحدة تفعل كل شيء
class BadUserProfileActivity {
fun loadUser(userId: Int) {
// طلب HTTP
// تحليل JSON
// تحديث UI
// حفظ في قاعدة البيانات
}
}
// بعد تطبيق SRP
class UserRepository {
fun getUser(userId: Int): User
}
class UserViewModel {
private val repo: UserRepository
fun loadUser(userId: Int) { }
}
class UserProfileFragment {
fun render(user: User) { }
}
مثال مماثل في iOS Swift مع فصل طبقة الشبكة والعرض:
// انتهاك SRP: ViewController يدير البيانات وUI
class BadProfileViewController: UIViewController {
func viewDidLoad() {
// طلب URLSession
// فك تشفير JSON
// تحدياث label
}
}
// بعد تطبيق SRP
protocol UserServiceProtocol {
func fetchUser(id: Int) async throws -> User
}
class ProfileViewModel {
private let service: UserServiceProtocol
func loadProfile(id: Int) { }
}
class ProfileViewController: UIViewController {
func display(user: User) { }
}
إعادة هندسة SRP لا تعقد المعمارية — إنها تعيد توزيع المسؤولية. قد ينخفض حجم الكود بشكل على إلغاء التكرار. لكل فئة جديدة غرض واضح ويمكن تطويرها بشكل مستقل.
يساعد التركيب في الحفاظ على SRP حيث تخلق الوراثة اقترانات غير ضرورية. بدلاً من فئة أساس مع عشرات الطرق، تتلقى الفئة الفرعية مجموعة من الكائنات المتخصصة عبر المنشئ. كل كائن مسؤول عن وظيفته الخاصة.
في تطوير Android، يسمح نمط Decorator بإضافة مسؤوليات دون تعديل الفئة الأصلية. في iOS، يفصل تسلسل Middleware في طبقة الشبكة التسجيل والتخزين المؤقت والمصادقة إلى وحدات منفصلة.
أكثر الانتهاكات شيوعًا هو الكائن العملاق: فئة تدير قاعدة البيانات وترسل الإشعارات وتنشئ التقارير وتعالج إدخالات المستخدم. تصبح هذه الفئة عنق زجاجة في المشروع: أي تغيير يتطلب اختبارات ارتداد كاملة.
في التطوير المحمول، يؤدي خلط منطق الأعمال ومنطق UI في Activity أو Fragment أو ViewController إلى انتهاك SRP. عندما يقوم onClickListener بالتحقق من البيانات واستدعاء API وتحديث ظهور الأزرار معًا — هذا انتهاك مباشر لمبدأ المسؤولية الواحدة.
تشمل عواقب انتهاك SRP: صعوبة التطوير المتوازي (تعارضات في ملف واحد)، صعوبة اختبارات الوحدة، ارتفاع تكلفة إجراء التغييرات وانخفاض قرائية الكود. تتطلب المشاريع ذات انتهاكات SRP المنتظمة 2-3 أضعاف الوقت لإضافة وظائف جديدة.
يمكن تحديد انتهاك SRP من خلال علامات غير مباشرة: تتجاوز الفئة 200 سطر، تستورد وحدات من طبقات مختلفة (UI + network + database)، لديها أكثر من 5 طرق عامة من مواضيع مختلفة. مقياس التماسك هو مؤشر إحصائي: الاختراق المنخفض للطرق داخل الفئة يشير إلى انتهاك SRP.
لكشف انتهاكات SRP، استخدم أدوات التحليل الثابت: لـ Android — Detekt مع قاعدة TooManyFunctions، ولـ iOS — SwiftLint مع قاعدة file_length. تُسلط هذه الأدوات الضوء على الفئات التي تتجاوز حدود الحجم والتعقيد.
تتم إعادة هندسة الفئات المنتهكة لـ SRP من خلال Extract Class أو Extract Delegate: يتم استخراج مجموعة من الطرق المترابطة إلى فئة منفصلة، وتفوض الفئة الأصلية بالاستدعاءات لها. تحول التطبيقات التدريجية لهذه إعادات الهندسة الكائن العملاق إلى مجموعة من الوحدات ضعيفة الاقتران، كل واحدة بمسؤولية واحدة. يسمح هذا النهج بتحسين المعمارية دون إيقاف التطوير — تتم إعادة الهندسة تكراريًا، وحدة واحدة في كل مرة.
الأسئلة الشائعة
لا. SRP ليس عن عدد الطرق، بل عن عدد أسباب التغيير. يمكن أن يكون للفئة عشرات الطرق إذا كانت تخدم مسؤولية واحدة أمام فاعل واحد. طريقة واحدة هي النقيض المقابل، التي تؤدي إلى تجزئة مفرطة للكود.
هما نفس المبدأ. يترجم Single Responsibility Principle إلى «المسؤولية الواحدة» و«الواجب الواحد». يعكس مصطلح «المسؤولية» الجوهر بشكل أكثر دقة: الأمر يتعلق بالمسؤولية أمام فاعل، وليس بوظيفة تقنية.
Repository هو نتيجة مباشرة لتطبيق SRP على طبقة البيانات. بدلاً من نشر منطق الوصول إلى البيانات عبر ViewModel أو UseCase، يتحمل Repository مسؤولية واحدة: توفير البيانات مع تجريد المصدر. هذا تنفيذ كلاسيكي لـ SRP في المعمارية المحمولة.
نعم، SRP لا يمنع الاعتمادات. يمكن للفئة ذات المسؤولية الواحدة أن تفوض بجزء من العمل لفئات أخرى عبر التركيب. المهم أن هذه المهام المفوضة تكون جزءًا من نفس المسؤولية، وليس سببًا مستقلاً للتغيير.
اطرح السؤال: «ما الفاعلون الذين قد يطلبون تغيير هذه الفئة؟» إذا كانت الإجابة تتضمن أكثر من فاعل واحد، فإن SRP منتهك. وبالإضافة: حاول وصف غرض الفئة بجملة واحدة بدون اداة «و». إذا لم تستطع، فالفئة تفعل الكثير.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا