مبدأ الـ Single Responsibility
السلام عليكم ورحمة الله وبركاته
يمكنك متابعة السلسلة بالترتيب أو الانتقال مباشرة إلى أي مقال:
المقدمة
نبدأ مع أول مبدأ من مبادئ الـ SOLID وهو الـ Single Responsibility
هو مبدأ يهتم بجمع مجموعة من الأكواد التي تتشارك في نفس الوظيفة ثم توحيدها تحت مظلة واحدة وتكون مسؤولة عن تلك الوظيفة
لكي تسهل اعادة استخدامها مجددًا في أماكن مختلفة
والمبدأ ينص على أن الكلاس الواحد يجب أن يكون مسؤول عن شيء ووظيفة واحدة
بالتالي كل الدوال التي بداخل هذا الكلاس يجب أن تخدم هذه الوظيفة التي يقوم بها هذا الكلاس
وطالما أن الكلاس يكون مسؤول عن وظيفة واحدة فقط فأنت عندما تريد إضافة شيء ما او تعدل شيء ما في الموضوع الفلاني فستذهب إلى الكلاس المسؤول عن ها الموضوع المعين
بمعنى إذا كان لديك كلاس يدعى EmailService ما الذي يخطر على بالك عندما تقرأ اسم الكلاس ؟
- هل يخطر على بالك أنه يقوم بإحضار بيانات المستخدمين ؟ بالطبع لا
- هل يخطر على بالك أنه يقوم بحساب كم نسبة شراء المستخدمين لمنتج معين ؟ أكيد لا
- هل يخطر على بالك أنه يقوم بتخزين إحصائيات زيارات المستخدمين في قاعدة البيانات ؟ أكيد لا
ما الذي يجعلك متأكدًا أن كلاس EmailService لا يقوم بتلك الأمور
بسبب أنه من الواضح من اسمه أنه يقوم بعمل بعض الأمور المتعلقة بالبريد الإلكتروني
أمور مثل إرسال رسائل عبر البريد الإلكتروني إلى المستخدمين مثل:
- إرسال رمز التحقق
OTPلتأكيد حسابه وبريده الإلكتروني - إرسال رسائل تنبيهية ومعلومات عن حالة الحساب
- تقوم بتجهيز وإحضار شكل الـ
htmlالتي سيتم إرساله - دالة تقوم بتفقد بشيء
- ... إلخ
فعندما تريد تعديل طريقة إرسالك للرسائل عبر البريد الالكتروني فأنت تلقائيًا تدرك أن الدالة المسؤولة عن هذا الأمر موجودة في كلاس الـ EmailService
أو عندما تريد عمل unit test للكلاس EmailService فأنت ستركز على شيء واحد فقط وهو الوظيفة الاساسية لكل دالة التي ستحوم حول فكرة ووظيفة كلاس الـ EmailService
ماذا عن كلاس يدعى ProductService ؟
- دالة تقوم بإحضار منتجات أو البحث عن منتجات معينة
- دالة تقوم بتطبيق بعض الخصومات على المنتج
- دالة تقوم بحساب السعر الكلي لمجموعة من المنتجات
- دالة تقوم ... بشيء خاص له علاقة بالمنتج ... إلخ
أظن أن الفكرة وصلت
مثال يناقض المبدأ
سنفترض أنك لديك كلاس يدعى UserService وسأعيد عليك السؤال عندما تقرأ اسم الكلاس ما الذي يخطر على بالك ؟
سيخطر على بالك دوال مثل getAllUsers, getOneUser, updateOneUser ... وإلى آخره من الدوال التي تخدم فكرة الـ UserService
class UserService {
public getAllUsers() {
// get all users
// ...
}
public getOneUser(id: string) {
// get one user
// ...
}
public signUp(email: string, password: string) {
// check email and password
// sign up the user
// ...
}
public login(email: string, password: string) {
// check email and password
// log in the user
// ...
}
public getHTMLTemplate(type: string) {
// get html template
// ...
}
public sendOTP(email: string, otp: string) {
// get html template
// send otp to email
// ...
}
}
لماذا هذا الكلاس يخالف المبدأ ؟
أريدك أن تحلل ما وظيفة الكلاس الأساسية وأريدك أن تحلل الدوال التي به هل هي تخدم الوظيفة الأساسية الذي يتبناها الكلاس ؟
ستجد أن الإجابة بالطبع لا، والسبب بكال وضوح أنه الكلاس مسؤول عن وظائف مختلفة
- الكلاس بالفعل به دوال مسؤولة عن الـ
UserServiceمثلgetAllUsersوgetOneUserوتحقق الوظيفة الأساسية للكلاس - لكن أيضًا به دوال مسؤولة عن الـ
AuthمثلsignInوlogin - وبه دوال مسؤولة عن الـ
EmailمثلsendOTPوgetHTMLTemplate
ستجد أن الكلاس البسيط UserService ربنا فتح عليه وأصبح مسؤولة عن وظائف متعددة في آن واحد
فتراه مسؤول عن كل ما يتعلق بالمستخدم
وتراه مسؤول عن الأمور الصلاحيات وتسجيل الأمان
وتراه مسؤول عن إرسال رسائل عبر البريد الإلكتروني
وهذا بالطبع يخالف مبدأ الـ Single Responsibility وهو أن كل كلاس يجب أن يكون مسؤول عن وظيفة واحدة
مثال يوافق المبدأ
الحل بكل بساطة أننا ننشيء كلاسات متعددة وكل كلاس يكون مسؤول عن وظيفة واحدة
- يمكننا أن نبقي الكلاس
UserServiceكما هو ويكون بهgetAllUsersوgetOneUserالتي تحقق الوظيفة الأساسية للكلاس
class UserService {
public getAllUsers() {
// get all users
// ...
}
public getOneUser(id: string) {
// get one user
// ...
}
// updateOneUser, deleteOneUser, getUserProfile ... etc.
}
- يمكننا أن ننشيء كلاس
AuthServiceبه الدوال المسؤولة عن الـAuthمثلsignInوloginوresetPasswordوverifyAccountو ... إلخ
class AuthService {
public signUp(email: string, password: string) {
// check email and password
// sign up the user
// ...
}
public login(email: string, password: string) {
// check email and password
// log in the user
// ...
}
// resetPassword, verifyAccount, forgotPassword ... etc.
}
- وننشيء كلاس
EmailServiceبه الدوال المسؤولة عن التعامل مع البريد الالكتروني مثلsendOTPوgetHTMLTemplateوsendEmailMessageو ... إلخ
class EmailService {
public getHTMLTemplate(type: string) {
// get html template
// ...
}
public sendOTP(email: string, otp: string) {
// get html template
// send otp to email
// ...
}
public sendEmailMessage(email: string, message: string) {
// get html template
// send email message
// ...
}
// ... etc.
}
فوائد المبدأ
هكذا كل كلاس يكون مسؤول عن وظيفة واحدة ومن يريد شيء يمكننه استدعاءه من الكلاس المسؤول عن هذا الشيء
فيمكن لكلاس الـ AuthService أن ينشيء object من EmailService ليستخدم أي دالة يريدها متى يشاء كـ sendOTP أو sendEmailMessage أو ... إلخ
class AuthService {
private emailService: EmailService;
constructor() {
const emailService = new EmailService();
}
public signUp(email: string, password: string) {
// check email and password
// sign up the user
// ...
this.emailService.sendOTP(email, "123456");
}
public verifyAccount(email: string) {
// check email
// verifying the account ...
// ...
this.emailService.sendEmailMessage(
email,
"Verified the account successfully",
);
}
// ... etc.
}
لاحظ أن الـ AuthService يقوم فقط بالتركيز على وظائفه الأساسية وإن أراد شيء ما من الـ EmailService يقوم باستدعاء فقط دون أن يهتم بالتفاصيل الخاصة بالـ EmailService والدوال التي به
وأي كلاس يحتاج شيء ما من الـ EmailService سيقوم فقط باستدعاء الدالة التي يريدها منه
فمثلا هنا دالة signUp كانت تريد أن ترسل OTP فبكل بساطة قامت باستدعاء الكلاس المسؤول عن إرسال الرسائل EmailService وقامت بتنفيذ الدالة التي ارادتها sendOTP
ودالة أخرى verifyAccount قامت بتنفيذ الدالة sendEmailMessage لإرسال رسالة معينة
فهنا الـ AuthService لم يقم بإنشاء دالة sendOTP أو sendEmailMessage عنده بل قام باستدعائها من الكلاس المسؤول عنها وهو الـ EmailService
هكذا نحن وحدنا وظائف كل كلاس وكل كلاس يحتوي على الدوال التي تخدم وظيفته وأي كلاس يحتاج شيء ما من كلاس آخر معين سيقوم فقط باستدعاء الكلاس الآخر المسؤول عن هذا الشيء
وإن حدثت مشكلة ما في الـ EmailService أو تريد تعديل إحدى دواله فأنت ستقوم بالتعديل في مكان واحد فقط وهو الكلاس EmailService دون أن تأثر أو تعدل شيء في باقي الكلاسات الأخرى كـ AuthService وغيره
فأنت تعدل فقط في الكلاس المسؤول عن هذا الشيء
الأمور هكذا أصبحت منظمة أكثر وتسهل علينا أمور عديدة في كتابة الكود وتبسيطه وتعديله وكل تلك الأمور الجميلة التي يتغنى بها الجميع
خلاصة مبدأ الـ Single Responsibility
باختصار، مبدأ الـ Single Responsibility يركز على على تقسيم الكلاسات إلى وحدات وظيفية مستقلة أي أن كل كلاس يجب أن يخدم على وظيفة واحدة فقط
مثل كلاس الـ UserService يجب أن يكون مسؤول عن كل شيء يتعلق بالمستخدم فقط
ولا يجب أن يكون مسؤول عن أي شيء آخر مثل الـ OrderService أو الـ ProductService أو غيرها
كلاس الـ NotificationService يجب أن يكون مسؤول عن إرسال الإشعارات فقط وهكذا ... إلخ
كل الدوال التي في الكلاس يجب أن تكون متعلقة بالوظيفة الرئيسية للكلاس التي تنتمي له
بمعنى لا تقوم بعمل دالة تدعى sendOTP في كلاس الـ AuthService لأنه ليس مسؤول عن ذلك
لكن يمكنك أن تنشيء object من EmailService داخل الـ AuthService وتستدعي الدالة sendOTP من خلال هذا الـ object هكذا:
class AuthService {
constructor(private emailService: EmailService) {}
public signup(email: string, password: string) {
// signup logic ...
this.emailService.sendOTP(email);
}
}
التعليقات
شاركنا رأيك في هذه المقالة أو اسأل عن أي شيء يخصها