ما هو Dependency Injection في TypeScript ولماذا هو مطلوب؟
Dependency Injection (DI) هو نمط تصميم يطبق مبدأ انعكاس التحكم (Inversion of Control, IoC). بدلاً من أن يقوم الكائن بإنشاء تبعياته بنفسه، يتم "حقنها" من الخارج. في TypeScript، هذا النمط مفيد بشكل خاص بفضل الكتابة الصارمة (strict typing) وإمكانيات الديكوراتور (decorators).
تخيل أنك تبني تطبيقًا لمتجر إلكتروني. لديك كلاس OrderService يجب أن يرسل إشعارات. بدون DI، سيكون مرتبطًا بشكل صارم بطريقة إرسال محددة (مثل Email). مع DI، يمكنك بسهولة استبدال Email بـ SMS أو إشعارات Push دون تغيير كود OrderService.
لماذا يجب استخدام DI في TypeScript؟
- قابلية الاختبار: سهولة استبدال الخدمات الحقيقية بكائنات mock في اختبارات الوحدة.
- المرونة: يصبح النظام معياريًا، ويمكن تغيير التبعيات دون تعديل كود العميل.
- تبسيط الصيانة: كل كلاس مسؤول فقط عن منطق أعماله، وليس عن إنشاء كائنات أخرى.
- أمان النوع: يتحقق TypeScript من الأنواع في مرحلة التجميع، مما يقلل من عدد الأخطاء أثناء التنفيذ.
تطبيق DI الأساسي في TypeScript بدون أطر عمل
لنبدأ بمثال بسيط. سننشئ واجهة (interface) لخدمة الإشعارات وتطبيقين لها.
// تعريف الواجهةinterface INotificationService { send(message: string): void;}
// تطبيق لـ Emailclass EmailNotificationService implements INotificationService { send(message: string): void { console.log(`إرسال Email: ${message}`); }}
// تطبيق لـ SMSclass SMSNotificationService implements INotificationService { send(message: string): void { console.log(`إرسال SMS: ${message}`); }}
// الكلاس الذي يستخدم DIclass OrderService { constructor(private notificationService: INotificationService) {}
processOrder(orderId: number): void { // منطق معالجة الطلب this.notificationService.send(`الطلب #${orderId} تمت معالجته`); }}
// الاستخدامconst emailService = new EmailNotificationService();const orderService = new OrderService(emailService);orderService.processOrder(123); // إرسال Email: الطلب #123 تمت معالجتهفي هذا المثال، لا يعرف OrderService أي خدمة إشعارات محددة يتم استخدامها. إنه ببساطة يستدعي طريقة send عبر الواجهة.
DI المتقدم: إنشاء حاوية IoC بسيطة
للمشاريع الكبيرة، يصبح الحقن اليدوي للتبعيات مرهقًا. هنا يأتي دور حاوية IoC. دعنا نرى كيفية تطبيقها باستخدام ديكوراتورات TypeScript.
الخطوة 1: قم بتفعيل دعم الديكوراتورات في ملف tsconfig.json
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true }}الخطوة 2: أنشئ حاوية DI
// di-container.tstype Constructor<T> = new (...args: any[]) => T;
class DIContainer { private services = new Map<string, any>();
register<T>(token: string, implementation: Constructor<T>): void { this.services.set(token, implementation); }
resolve<T>(token: string): T { const Implementation = this.services.get(token); if (!Implementation) { throw new Error(`الخدمة "${token}" غير مسجلة`); }
// الحصول على أنواع معاملات المُنشئ عبر الانعكاس (reflection) const paramTypes: Constructor<any>[] = Reflect.getMetadata('design:paramtypes', Implementation) || [];
// حل التبعيات بشكل تكراري const dependencies = param