حقن التبعية في TypeScript: من النظرية إلى التطبيق
\n\nحقن التبعية (Dependency Injection - DI) هو أحد أنماط التصميم الأساسية التي تتيح إنشاء تطبيقات مرنة وقابلة للاختبار والصيانة. في TypeScript، بفضل الكتابة الصارمة (strict typing) وإمكانيات المُزينات (decorators)، يصبح تنفيذ DI أنيقاً بشكل خاص. في هذه المقالة، سنشرح كيفية تطبيق حقن التبعية عملياً باستخدام TypeScript، وسنتناول لماذا يُعتبر هذا النمط حجر الزاوية في التطوير الحديث.
\n\nما هو حقن التبعية ولماذا هو ضروري؟
\n\nحقن التبعية (Dependency Injection) هو نمط يتم فيه استلام الكائن لتبعياته من الخارج، بدلاً من إنشائها بنفسه. بدلاً من كتابة new SomeService() داخل الفئة، نقوم بتمرير نسخة جاهزة عبر المُنشئ (constructor) أو الطريقة (method).
المزايا الرئيسية لـ DI:
\n- \n
- تقليل الاقتران (Coupling) — لا تعتمد الفئات على تطبيقات محددة، بل فقط على التجريدات (الواجهات). \n
- تبسيط الاختبار — يمكننا بسهولة استبدال الخدمات الحقيقية بـ mocks أو stubs. \n
- زيادة قابلية إعادة الاستخدام — يمكن استبدال التبعيات دون تغيير كود المستهلك. \n
- الامتثال لمبدأ عكس التبعية (DIP من SOLID) — لا تعتمد الوحدات عالية المستوى على الوحدات منخفضة المستوى. \n
تنفيذ حقن التبعية في TypeScript بدون أطر عمل
\n\nلنبدأ بمثال بسيط. لنفترض أن لدينا خدمة تسجيل (Logger) وفئة تستخدمها.
\n\n// بدون DI - اقتران صارم\nclass Logger {\n log(message: string): void {\n console.log(`[LOG]: ${message}`);\n }\n}\n\nclass UserService {\n private logger: Logger;\n\n constructor() {\n this.logger = new Logger(); // مشكلة: اعتماد صارم\n }\n\n createUser(name: string): void {\n this.logger.log(`تم إنشاء المستخدم: ${name}`);\n // ... منطق الإنشاء\n }\n}\n\n// مع DI - عبر المُنشئ\ninterface ILogger {\n log(message: string): void;\n}\n\nclass ConsoleLogger implements ILogger {\n log(message: string): void {\n console.log(`[CONSOLE LOG]: ${message}`);\n }\n}\n\nclass FileLogger implements ILogger {\n log(message: string): void {\n // منطق الكتابة إلى ملف\n console.log(`[FILE LOG]: ${message}`);\n }\n}\n\nclass UserServiceWithDI {\n constructor(private logger: ILogger) {} // يتم حقن التبعية من الخارج\n\n createUser(name: string): void {\n this.logger.log(`تم إنشاء المستخدم: ${name}`);\n }\n}\n\n// الاستخدام\nconst consoleLogger = new ConsoleLogger();\nconst userService = new UserServiceWithDI(consoleLogger);\nuserService.createUser('Alice'); // [CONSOLE LOG]: تم إنشاء المستخدم: Alice\n\n\nكما ترى، الآن UserServiceWithDI لا يعرف أي مسجل (logger) محدد يُستخدم. يمكننا بسهولة استبداله بـ FileLogger أو mock للاختبارات.
DI متقدم: إنشاء حاوية IoC بسيطة
\n\nعندما تكثر التبعيات، يصبح الحقن اليدوي عبر المُنشئ مرهقاً. هنا تأتي حاوية IoC (Inversion of Control Container) للإنقاذ. وهي تدير إنشاء الكائنات ودورة حياتها.
\n\nلننفذ حاوية بسيطة في TypeScript:
\n\n// نوع لمصنع التبعيات\ntype Factory<T> = (container: Container) => T;\n\nclass Container {\n private factories = new Map<string, Factory<any>>();\n private instances = new Map<string, any>();\n\n // تسجيل التبعية (يتم استدعاء المصنع عند كل طلب)\n register<T>(name: string, factory: Factory<T>): void {\n this.factories.set(name, factory);\n }\n\n // تسجيل singleton (يتم استدعاء المصنع مرة واحدة)\n singleton<T>(name: string, factory: Factory<T>): void {\n this.factories.set(name, factory);\n }\n