مقدمة: ما هو حقن التبعية ولماذا هو مطلوب؟
في عالم تطوير البرمجيات، غالبًا ما نواجه موقفًا حيث تعتمد فئة (أو وحدة) على أخرى. على سبيل المثال، قد تستخدم فئة UserService فئة DatabaseService لحفظ البيانات. النهج التقليدي هو إنشاء مثيل لـ DatabaseService مباشرة داخل UserService. يؤدي هذا إلى ارتباط قوي في الكود، مما يعقد اختباره وصيانته وتوسيعه.
حقن التبعية (DI) هو نمط تصميم يحل هذه المشكلة. بدلاً من إنشاء التبعيات داخل الفئة، يتم "حقنها" من الخارج. هذا يحقق مبدأ انعكاس التحكم (IoC): لا تدير الفئة تبعياتها، بل يوفرها كود خارجي (حاوية أو جهة مستدعية).
في هذه المقالة، سنشرح كيفية تنفيذ حقن التبعية في TypeScript بأمثلة بسيطة. ستتعرف على سبب اعتبار DI ممارسة جيدة وكيف يساعد في كتابة كود نظيف وقابل للاختبار.
المشكلة: الارتباط القوي بدون DI
لنبدأ بمثال يوضح كيف لا يجب كتابة الكود. تخيل أن لدينا تطبيقًا يرسل إشعارات للمستخدمين.
// بدون حقن التبعيةclass EmailService { sendEmail(to: string, message: string): void { console.log(`إرسال بريد إلكتروني إلى ${to}: ${message}`); // ... منطق الإرسال الفعلي }}
class NotificationService { private emailService: EmailService;
constructor() { // المشكلة: ارتباط قوي this.emailService = new EmailService(); }
notifyUser(userEmail: string, message: string): void { this.emailService.sendEmail(userEmail, message); }}
// الاستخدامconst notifier = new NotificationService();notifier.notifyUser('user@example.com', 'Hello!');في هذا الكود، NotificationService مرتبط بقوة بـ EmailService. ماذا لو أردنا إرسال رسائل نصية قصيرة (SMS) أو إشعارات فورية (push)؟ سيتعين علينا تعديل كود NotificationService. بالإضافة إلى ذلك، من الصعب اختبار NotificationService — لا يمكننا استبدال EmailService الحقيقي بكائن وهمي (mock).
الحل: حقن التبعية عبر المُنشئ
أبسط وأشهر طريقة لتنفيذ DI هي تمرير التبعيات عبر المُنشئ. هذا يجعل الكود مرنًا وسهل الاختبار.
أولاً، نحدد واجهة (interface) لخدمة الإشعارات الخاصة بنا. هذه خطوة أساسية: نحن نبرمج على مستوى التجريدات، وليس على التطبيقات الملموسة.
// تعريف واجهة لأي خدمة إشعاراتinterface INotificationService { send(to: string, message: string): void;}
// تطبيق للبريد الإلكترونيclass EmailService implements INotificationService { send(to: string, message: string): void { console.log(`[EMAIL] أُرسل إلى ${to}: ${message}`); }}
// تطبيق للرسائل النصيةclass SMSService implements INotificationService { send(to: string, message: string): void { console.log(`[SMS] أُرسل إلى ${to}: ${message}`); }}
// الفئة التي تستخدم DIclass UserNotifier { private notifier: INotificationService;
// يتم حقن التبعية عبر المُنشئ constructor(notifier: INotificationService) { this.notifier = notifier; }
notifyUser(userContact: string, message: string): void { this.notifier.send(userContact, message); }}
// الاستخدامconst emailNotifier = new UserNotifier(new EmailService());emailNotifier.notifyUser('alice@example.com', 'طلبك جاهز!');
const smsNotifier = new UserNotifier(new SMSService());smsNotifier.notifyUser('+7-999-123-45-67', 'رمز التحقق: 1234');الآن، UserNotifier لا يعرف أي خدمة محددة يتم استخدامها.