حقن التبعية في JavaScript: ما هو ولماذا هو مهم
في عالم التطوير الحديث باستخدام JavaScript (Node.js)، تصبح نظافة الكود وقابليته للصيانة أمرًا بالغ الأهمية. أحد المبادئ الأساسية التي تساعد في تحقيق ذلك هو حقن التبعية (DI). تم إنشاء هذه المقالة خصيصًا للمبتدئين لشرح المفهوم بلغة بسيطة مع أمثلة عملية.
تخيل أنك تقوم بتجميع جهاز كمبيوتر. بدلاً من لحام بطاقة الرسومات مباشرة في اللوحة الأم بشكل دائم، تستخدم فتحات PCI-E. هذا يسمح لك باستبدال بطاقة الرسومات بسهولة بأخرى أقوى أو استخدام أخرى للاختبار. يعمل حقن التبعية بنفس المبدأ: أنت لا تنشئ التبعيات داخل الفئة (لا "تلحمها")، بل تقوم بتمريرها من الخارج.
المشكلة بدون حقن التبعية
لننظر إلى مثال نموذجي في Node.js، حيث يصبح الكود مقترنًا بشدة (tightly coupled):
class Logger { log(message) { console.log(`[LOG]: ${message}`); }}
class UserService { constructor() { this.logger = new Logger(); // تبعية صلبة }
createUser(name) { this.logger.log(`تم إنشاء المستخدم: ${name}`); // منطق إنشاء المستخدم }}
const service = new UserService();service.createUser('آنا');هنا، UserService يقوم بنفسه بإنشاء مثيل من Logger. إذا أردنا استبدال console.log بالكتابة إلى ملف أو قاعدة بيانات، سيتعين علينا تعديل كود UserService. هذا ينتهك مبدأ الانفتاح/الإغلاق (Open/Closed Principle) من SOLID.
أساسيات حقن التبعية في JavaScript
ما هو حقن التبعية؟
حقن التبعية (DI) هو نمط تصميم حيث يتلقى الكائن تبعياته من مصدر خارجي، بدلاً من إنشائها بنفسه. في سياق JavaScript، يعني هذا تمرير التبعيات عبر المُنشئ (constructor) أو المُحدد (setter) أو معامل الدالة.
ثلاث طرق رئيسية لحقن التبعيات
في JavaScript و Node.js، يتم استخدام ثلاث طرق:
- حقن المُنشئ (Constructor Injection): يتم تمرير التبعيات عبر مُنشئ الفئة.
- حقن المُحدد (Setter Injection): يتم تعيين التبعيات عبر دالة منفصلة (مُحدد).
- حقن الدالة (Method Injection): يتم تمرير التبعيات مباشرة إلى الدالة التي تستخدمها.
الأكثر شيوعًا وموصى به هو حقن المُنشئ، لأنه يجعل التبعيات واضحة وغير قابلة للتغيير بعد إنشاء الكائن.
أمثلة عملية لحقن التبعية في Node.js
مثال 1: حقن المُنشئ
لنعد كتابة مثالنا باستخدام حقن التبعية:
class Logger { log(message) { console.log(`[LOG]: ${message}`); }}
class FileLogger { log(message) { // محاكاة الكتابة إلى ملف console.log(`[FILE]: ${message}`); }}
class UserService { constructor(logger) { this.logger = logger; // التبعية تُمرر من الخارج }
createUser(name) { this.logger.log(`تم إنشاء المستخدم: ${name}`); // منطق إنشاء المستخدم }}
// الاستخدام مع مسجلات مختلفةconst consoleLogger = new Logger();const fileLogger = new FileLogger();
const serviceWithConsole = new UserService(consoleLogger);serviceWithConsole.createUser('آنا'); // [LOG]: تم إنشاء المستخدم: آنا
const serviceWithFile = new UserService(fileLogger);serviceWithFile.createUser('إيفان'); // [FILE]: تم إنشاء المستخدم: إيفانالآن UserService لا يعرف أي مسجل يتم استخدامه. نحن