مبدأ الـ Dependency Inversion
السلام عليكم ورحمة الله وبركاته
يمكنك متابعة السلسلة بالترتيب أو الانتقال مباشرة إلى أي مقال:
المقدمة
وأخيرًا نصل إلى المبدأ الخامس والأخير من مبادئ الـ SOLID وهو مبدأ الـ Dependency Inversion
هذا المبدأ يركز على منع الاعتماد المباشر بين الكلاسات
بل يجب أن تعتمد على الـ Interface أو على الـ Abstract Class
بمعنى أنه يجب أن تعتمد على الـ Interface بدلًا من الكلاسات المحددة بذاتها
فإن كان هناك كلاس يدعى NotificationService ويعتمد على كلاس EmailService و SMSService بشكل مباشر فهذا خطأ
لأنه فرضًا أننا تخلينا عن الـ EmailService أو أضفنا أنواع جديدة من الخدمات مثل PushNotificationService أو WhatsAppService هل ستذهب وتعدل الـ NotificationService ليعتمد على الخدمات الجديدة؟ ماذا لو اضفنا 10 خدمات هل ستنشيء object لكل خدمة داخل الـ NotificationService؟
بالطبع لا، لذا يجب أن تجعل الـ NotificationService يعتمد على الـ interface أو abstract class بدلًا من الكلاسات المحددة بذاتها
هذا الـ interface قد يدعى INotifiableProvider ويحتوي على دالة أساسية مثل notify
ثم تجعل الكلاس NotificationService يعتمد على object من هذا الـ interface بدلًا من الكلاسات المحددة بذاتها
ثم تقوم بإنشاء كلاسات تبني هذا الـ interface الـ INotifiableProvider مثل EmailService و SMSService و PushNotificationService و WhatsAppService وغيرها
هكذا يمكنك أن تضيف أي خدمة جديدة دون الحاجة لتعديل الـ NotificationService
الأمر مشابه لمبدأ الـ Open/Closed بعض الشيء لكنه يركز على الاعتماد المباشر بين الكلاسات وفصلها
مثال يناقض المبدأ
تخيل أن لديك كلاس يدعى OrderService مسؤول عن طلبات المنتجات
وهذا الكلاس يعتمد على كلاسات محددة بذاتها مثل FoodProductService و ClothesProductService و ElectricProductService
class OrderService {
public foodProductService: FoodProductService;
public clothesProductService: ClothesProductService;
public electricProductService: ElectricProductService;
constructor() {
foodProductService = new FoodProductService();
clothesProductService = new ClothesProductService();
electricProductService = new ElectricProductService();
}
public orderFoodProduct() {
foodProductService.order();
}
public orderClothesProduct() {
clothesProductService.order();
}
public orderElectricProduct() {
electricProductService.order();
}
}
let orderService = new OrderService();
// it will create three objects
// FoodProductService, ClothesProductService, ElectricProductService
// but we need only need FoodProductService here
orderService.orderFoodProduct();
لاحظ أن الـ OrderService يعتمد على كلاسات محددة بذاتها مثل FoodProductService و ClothesProductService و ElectricProductService
فهو يقوم بإنشاء object من كل كلاس داخل الـ constructor ويعتمد عليها
ويقوم بعمل دالة مخصصة للتعامل مع كل منتج بشكل منفصل
أظنك لاحظت العديد من المشاكل هنا منها
- ماذا إن أردنا استخدام
objectمنFoodProductServiceفقط، لما يتم إنشاءobjectمنClothesProductServiceوElectricProductServiceونحن قد لا نحتاجهما - ماذا إن أردنا استخدام منتجات أخرى مثل
BooksProductServiceهل سنقوم بإضافةobjectجديد لكل منتج جديد؟ ودوال أخرى مخصصة له ونعدل هنا وهناك ؟ - ماذا إن أردنا الإستغناء عن نوع معين مثل
ClothesProductServiceهل سنذهب إلىOrderServiceونقوم بالتخلص من كل شيء متعلق به ونعدل الكلاسات والدوال ؟
هنا العلاقة بين الـ OrderService وبين الـ FoodProductService والـ ClothesProductService والـ ElectricProductService لا تتبع مبدأ الـ Dependency Inversion لأن الـ OrderService يعتمد على هذه الكلاسات اعتماد كلي وبشكل مباشر
مثال يوافق المبدأ
الحل بسيط وهو جعل الـ OrderService يعتمد على interface بدلًا من الكلاسات المحددة بذاتها
يمكن للـ OrderService أن يستقبل object من الـ interface من خلال الـ constructor ويعتمد عليه
ويمكنك أن تنشئ كلاسات تنفذ هذا الـ interface ثم عندما تستدعي الـ OrderService تقوم بإرسال object من كلاس المنتجات التي تريده فقط
لنقم بإنشاء interface يدعى IProductService ويحتوي على دالة order
interface IProductService {
order(): void;
// ... etc.
}
الآن يمكنك أن تنشئ كلاسات تنفذ هذا الـ interface مثل FoodProductService و ClothesProductService و ElectricProductService وغيرها
class FoodProductService implements IProductService {
public order(): void {
console.log("Ordering food product");
}
}
class ClothesProductService implements IProductService {
public order(): void {
console.log("Ordering clothes product");
}
}
class ElectricProductService implements IProductService {
public order(): void {
console.log("Ordering electric product");
}
}
الآن يمكننا تعديل الـ OrderService ليعتمد على الـ IProductService الـ interface بدلًا من الكلاسات المحددة بذاتها
class OrderService {
public productService: IProductService;
constructor(productService: IProductService) {
this.productService = productService;
}
public orderProduct() {
this.productService.order();
}
}
الآن يمكنك أن تنشئ object من الـ OrderService وترسل أي object من ينتمي إلى عائلة الـ IProductService
let foodProductService = new FoodProductService();
let orderService = new OrderService(foodProductService);
orderService.orderProduct(); // Ordering food product
///////////////////////////////////////
let clothesProductService = new ClothesProductService();
let orderService = new OrderService(clothesProductService);
orderService.orderProduct(); // Ordering clothes product
///////////////////////////////////////
let electricProductService = new ElectricProductService();
let orderService = new OrderService(electricProductService);
orderService.orderProduct(); // Ordering electric product
لاحظ كيف أننا عندما ننشيء object من الـ OrderService نقوم بإرسال فقط object من الكلاس المنتج الذي نريده
ويكون الـ OrderService يعتمد على الـ IProductService بغض النظر عن نوعه، اعتمادية مجردة من أي تفاصيل
هكذا يمكنك أن تضيف أي خدمة جديدة في كالاس جديد دون الحاجة لتعديل أي شيء في الـ OrderService
هذا يجعل الكود أكثر مرونة وسهولة في الصيانة
خلاصة مبدأ الـ Dependency Inversion
المبدأ يركز على منع الاعتماد المباشر بين الكلاسات بمعنى أنه لا يأتي كلاس معين يعتمد على كلاسات محددة بذاتها
مثل أن يعتمد كلاس مثل NotificationService ويعتمد على كلاسات بعينها مثل EmailService و SMSService ويعتمد على هذه الأنواع بشكل مباشر
class NotificationService {
public emailService: EmailService;
public smsService: SMSService;
constructor() {
this.emailService = new EmailService();
this.smsService = new SMSService();
}
public sendEmail() {
this.emailService.send();
}
public sendSMS() {
this.smsService.send();
}
}
هذا الذي تراه الآن هو يتعارض مع المبدأ لأن الـ NotificationService يعتمد على كلاسات محددة بذاتها
ولأنه فرضًا لو تخلينا عن أحد هذه الكلاس أوأضفنا أنواع جديدة من الكلاسات فماذا ستفعل ؟
هل ستذهب وتعدل الـ NotificationService ليعتمد على هذه الكلاسات الجديدة؟ وتزيل القديم التي لم تعد تحتاجها؟
بالطبع لا، لذا يجب أن تجعل الـ NotificationService يعتمد على الـ interface أو abstract class بدلًا من الكلاسات المحددة بذاتها
هذا interface قد يدعى INotifiableProvider ويحتوي على دالة أساسية مثل notify
ثم تجعل الكلاس NotificationService يعتمد على object من هذا الـ interface بدلًا من الكلاسات المحددة بذاتها
interface INotifiableProvider {
notify(): void;
}
class NotificationService {
public notifiableProvider: INotifiableProvider;
constructor(notifiableProvider: INotifiableProvider) {
this.notifiableProvider = notifiableProvider;
}
public sendNotification() {
this.notifiableProvider.notify();
}
}
الآن يمكنك أن تقوم بإنشاء كلاسات تبني الـ INotifiableProvider مثل EmailService و SMSService و PushNotificationService و WhatsAppService وغيرها
وترسل ما تريده إلى الـ NotificationService وتجعله يقوم بالعمل بشكل طبيعي
let emailService = new EmailService();
let notificationService = new NotificationService(emailService);
notificationService.sendNotification();
///////////////////////////////////////
let smsService = new SMSService();
let notificationService = new NotificationService(smsService);
notificationService.sendNotification();
///////////////////////////////////////
let pushNotificationService = new PushNotificationService();
let notificationService = new NotificationService(pushNotificationService);
notificationService.sendNotification();
الآن يمكنك أن تزيل أو تضيف أي خدمة دون الحاجة لتعديل أي شيء في الـ NotificationService
التعليقات
شاركنا رأيك في هذه المقالة أو اسأل عن أي شيء يخصها