حقن التبعية في TypeScript: دليل عملي مع أمثلة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

حقن التبعية في TypeScript: من النظرية إلى التطبيق

\n\n

حقن التبعية (Dependency Injection - DI) هو أحد أنماط التصميم الأساسية التي تتيح إنشاء تطبيقات مرنة وقابلة للاختبار والصيانة. في TypeScript، بفضل الكتابة الصارمة (strict typing) وإمكانيات المُزينات (decorators)، يصبح تنفيذ DI أنيقاً بشكل خاص. في هذه المقالة، سنشرح كيفية تطبيق حقن التبعية عملياً باستخدام TypeScript، وسنتناول لماذا يُعتبر هذا النمط حجر الزاوية في التطوير الحديث.

\n\n

ما هو حقن التبعية ولماذا هو ضروري؟

\n\n

حقن التبعية (Dependency Injection) هو نمط يتم فيه استلام الكائن لتبعياته من الخارج، بدلاً من إنشائها بنفسه. بدلاً من كتابة new SomeService() داخل الفئة، نقوم بتمرير نسخة جاهزة عبر المُنشئ (constructor) أو الطريقة (method).

\n\n

المزايا الرئيسية لـ DI:

\n
    \n
  • تقليل الاقتران (Coupling) — لا تعتمد الفئات على تطبيقات محددة، بل فقط على التجريدات (الواجهات).
  • \n
  • تبسيط الاختبار — يمكننا بسهولة استبدال الخدمات الحقيقية بـ mocks أو stubs.
  • \n
  • زيادة قابلية إعادة الاستخدام — يمكن استبدال التبعيات دون تغيير كود المستهلك.
  • \n
  • الامتثال لمبدأ عكس التبعية (DIP من SOLID) — لا تعتمد الوحدات عالية المستوى على الوحدات منخفضة المستوى.
  • \n
\n\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 للاختبارات.

\n\n

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                

المدونات

توصيات الكتب