حاوية حقن التبعية في C++: مكتبة Fruit لحقن التبعيات

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

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

ابدأ الدورة

حاوية حقن التبعية للغة C++: نظرة عامة على مكتبة Fruit



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



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

حاوية حقن التبعية هي أداة تتحمل مسؤولية إنشاء وربط الكائنات (التبعيات). بدلاً من أن تقوم الفئة بإنشاء تبعياتها بنفسها عبر new أو المصانع، تقوم الحاوية بـ"حقنها" من الخارج. وهذا يحقق مبدأ عكس التحكم (IoC) ويجعل الكود ضعيف الاقتران.



لماذا Fruit؟

مكتبة Fruit (https://github.com/google/fruit) تم تطويرها في Google وتتميز عن البدائل (مثل Boost.DI) بالميزات التالية:

  • التحقق في وقت الترجمة: يتم التحقق من جميع الروابط في مرحلة الترجمة، مما يستبعد أخطاء وقت التشغيل المتعلقة بغياب التبعيات.
  • أدنى حد من الحمل الزائد: لا تستخدم Fruit RTTI أو الاستدعاءات الافتراضية، مما يولد كودًا فعالاً.
  • البساطة: تتطلب فقط C++14 وملفات الرأس - لا وحدات ماكرو أو توليد كود.
  • دعم المكونات: يمكن بناء رسم بياني للتبعيات من وحدات قابلة لإعادة الاستخدام.


التثبيت

يتم توزيع Fruit كمكتبة header-only (تتطلب فقط fruit.h). للتثبيت عبر vcpkg:

vcpkg install fruit

أو قم باستنساخ المستودع وأضف الملف include/fruit/fruit.h.



المفاهيم الأساسية

في Fruit يتم استخدام ثلاثة عناصر رئيسية:

  • الحاقن (Injector) - الحاوية التي تقوم بإنشاء الكائنات.
  • المكون (Component) - وحدة تسجل الأنواع ومصانعها.
  • التعليقات التوضيحية - للتمييز بين التطبيقات المختلفة لنفس الواجهة.


مثال على الاستخدام

لنأخذ مثالًا كلاسيكيًا: خدمة تعتمد على مستودع. سننشئ واجهة وتطبيقين:



// interfaces.hpp#include <fruit/fruit.h>#include <string>

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

class IUserRepository {public: virtual std::string getUser(int id) = 0; virtual ~IUserRepository() = default;};


التطبيقات:

// implementations.cpp#include "interfaces.hpp"#include <iostream>

class ConsoleLogger : public ILogger {public: INJECT(ConsoleLogger()) = default; // تعليق Fruit التوضيحي للمنشئ void log(const std::string& msg) override { std::cout << "[LOG] " << msg << std::endl; }};

class SqlRepository : public IUserRepository {public: INJECT(SqlRepository()) = default; std::string getUser(int id) override { return "User_" + std::to_string(id); }};


الخدمة التي تستخدم كلا التبعيتين:

// user_service.cpp#include "interfaces.hpp"

class UserService {private: ILogger* logger; IUserRepository* repo;public: INJECT(UserService(ILogger* logger, IUserRepository* repo)) : logger(logger), repo(repo) {} void processUser(int id) { auto name = repo->getUser(id); logger->log("Processing user: " + name); }};


الآن سنقوم بتجميع المكون

توصيات