حقن التبعيات (Dependency Injection) في لغة C++: دليل كامل مع أمثلة

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

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

ابدأ الدورة

مقدمة إلى Dependency Injection في C++



في التطوير الحديث بلغة C++، أحد المبادئ الأساسية لإنشاء كود مرن وقابل للاختبار هو Dependency Injection (DI) — حقن التبعيات. يسمح نمط التصميم هذا بتقليل الاقتران بين مكونات النظام، مما يجعل الكود أكثر نمطية، وقابلية لإعادة الاستخدام، وأسهل للاختبار.



في هذه المقالة، سنشرح ما هو Dependency Injection في C++، وما المشكلات التي يحلها، وسنستعرض الطرق المختلفة لتطبيقه عمليًا مع أمثلة كود تساعدك على تطبيق هذا الأسلوب في مشاريعك.



مشكلة الاعتماد المباشر



قبل الحديث عن الحل، دعنا نلقي نظرة على مشكلة نموذجية. تخيل أن لدينا كلاس UserService يستخدم Logger لتسجيل السجلات:



class Logger {public:    void log(const std::string& message) {        std::cout << "[LOG]: " << message << std::endl;    }};

class UserService {private: Logger logger; // اعتماد صارمpublic: void createUser(const std::string& name) { logger.log("Creating user: " + name); // ... منطق إنشاء المستخدم }};


هنا، يقوم UserService بإنشاء نسخة من Logger بنفسه. هذا يخلق عدة مشكلات:

  • صعوبة الاختبار: لا يمكن استبدال الـ Logger بكائن mock أثناء اختبارات الوحدة.
  • اقتران صارم: تغيير مُنشئ Logger سيتطلب تغيير جميع الكلاسات التي تستخدمه.
  • انتهاك SRP (مبدأ المسؤولية الواحدة): الكلاس مسؤول ليس فقط عن منطقه الخاص، بل أيضًا عن إنشاء التبعيات.


ما هو Dependency Injection؟



Dependency Injection هو نمط يتم فيه تزويد الكائن بتبعياته من الخارج، بدلاً من أن يقوم بإنشائها بنفسه. الفكرة الأساسية: "لا تسأل، بل احصل".



في C++، هناك ثلاث طرق رئيسية لحقن التبعيات:

  • Constructor Injection — عبر المُنشئ.
  • Setter Injection — عبر دوال setter.
  • Interface Injection — عبر واجهة.


1. Constructor Injection (الحقن عبر المُنشئ)



هذه هي الطريقة الأكثر شيوعًا والمفضلة. يتم تمرير التبعيات إلى مُنشئ الكلاس:



#include <memory>#include <iostream>

class ILogger {public: virtual ~ILogger() = default; virtual void log(const std::string& message) = 0;};

class ConsoleLogger : public ILogger {public: void log(const std::string& message) override { std::cout << "[CONSOLE]: " << message << std::endl; }};

class FileLogger : public ILogger {public: void log(const std::string& message) override { // منطق الكتابة إلى ملف std::cout << "[FILE]: " << message << std::endl; }};

class UserService {private: std::unique_ptr<ILogger> logger_; // اعتماد عبر واجهةpublic: // Constructor Injection explicit UserService(std::unique_ptr<ILogger> logger) : logger_(std::move(logger)) {}

void createUser(const std::string& name) { logger_->log("Creating user: " + name); // ... منطق }};

int main() { auto consoleLogger = std::make_unique<ConsoleLogger>(); UserService service(std::move(consoleLogger)); service.createUser("Alice"); return 0;}


المزايا:

  • الاعتماد واضح في توقيع المُنشئ.
  • الكائن يكون دائمًا في حالة صالحة بعد الإنشاء.
  • سهولة الاختبار — يمكن استبدال

المدونات

توصيات الكتب