حاويات حقن التبعية لـ Rust: إدارة التبعيات في البرمجة النظامية
\n\nفي عالم البرمجة النظامية، تشتهر Rust بأدائها العالي وأمان الذاكرة. ومع ذلك، مع نمو المشروع، تظهر مهمة إدارة الرسوم البيانية المعقدة للتبعيات. على عكس اللغات ذات إدارة الذاكرة التلقائية (Java, C#, Python)، تتطلب Rust نهجًا خاصًا لحقن التبعيات (Dependency Injection, DI). في هذه المقالة، سنستعرض الأدوات والمكتبات الموجودة لتنفيذ حاويات DI في Rust، وكيفية استخدامها بشكل صحيح.
\n\nلماذا نحتاج إلى حاوية DI في Rust؟
\n\nيسمح نمط حقن التبعية بفصل إنشاء الكائنات عن استخدامها. في Rust، هذا مهم بشكل خاص بسبب نظام الملكية والاقتراض الصارم. تحل حاوية DI عدة مهام:
\n\n- \n
- تبسيط الاختبار — إمكانية استبدال التطبيقات الحقيقية بـ mocks \n
- تقليل الترابط في الكود — تعتمد المكونات على التجريدات وليس على التطبيقات الملموسة \n
- إدارة مركزية لدورة الحياة — الكائنات المفردة (singletons)، الكائنات المؤقتة، التجمعات \n
- الحل التلقائي للتبعيات — تقوم الحاوية ببناء الرسم البياني للكائنات بنفسها \n
المكتبات الرئيسية لـ DI في Rust
\n\n1. shaku — حاوية DI بسيطة وفعالة
\n\nواحدة من أشهر المكتبات. تعتمد على traits والماكرو. تسمح بتسجيل المكونات بأعمار مختلفة.
\n\n// Cargo.toml\n// [dependencies]\n// shaku = "0.6"\n\nuse shaku::{module, HasComponent, Interface};\nuse std::sync::Arc;\n\n// تعريف الواجهة\ntrait Logger: Interface {\n fn log(&self, msg: &str);\n}\n\n// التطبيق\n#[derive(Clone)]\nstruct ConsoleLogger;\nimpl Logger for ConsoleLogger {\n fn log(&self, msg: &str) {\n println!("LOG: {}", msg);\n }\n}\n\n// وحدة التسجيل\nmodule! {\n MyModule {\n components = [ConsoleLogger],\n providers = []\n }\n}\n\n// الاستخدام\nfn main() {\n let module = MyModule::builder().build();\n let logger: Arc<dyn Logger> = module.resolve().unwrap();\n logger.log("Hello from DI!");\n}\n\n2. dyn-clone + حاوية يدوية
\n\nإذا كنت تفضل البساطة، يمكنك إنشاء حاوية بسيطة يدويًا باستخدام Any و dyn-clone. هذا يمنحك تحكمًا كاملاً في العملية.
use std::any::Any;\nuse std::collections::HashMap;\n\nstruct SimpleContainer {\n services: HashMap<String, Box<dyn Any>>,\n}\n\nimpl SimpleContainer {\n fn new() -> Self {\n SimpleContainer { services: HashMap::new() }\n }\n\n fn register<T: 'static>(&mut self, service: T) {\n let type_name = std::any::type_name::<T>().to_string();\n self.services.insert(type_name, Box::new(service));\n }\n\n fn resolve<T: 'static>(&self) -> Option<&T> {\n let type_name = std::any::type_name::<T>().to_string();\n self.services.get(&type_name)\n .and_then(|s| s.downcast_ref::<T>())\n }\n}\n\n3. once_cell و lazy_static — للكائنات المفردة (singletons)
\n\nغالبًا ما يقتصر DI على إدارة الحالات العامة. تساعد هذه المكتبات في إنشاء كائنات مفردة يتم تهيئتها بشكل كسول (lazy).
\n\nuse once_cell::sync::Lazy;\nuse std::sync::Mutex;\n\nstatic CONFIG: Lazy<Mutex<Config>> = Lazy::new(|| {\n Mutex::new(Config::load())\n});\n\nstruct Config {\n db_url: String,\n}\n\nimpl Config {\n fn load() -> Self {\n Config { db_url: "postgres://localhost".into() }\n }\n}\n\nالأنماط المعمارية لـ DI في Rust
\n\nفي Rust، غالبًا ما يتم استخدام Constructor Injection — حيث يتم تمرير التبعيات عبر مُنشئ الهيكل. هذا هو الأسلوب الأكثر توافقًا مع
أيضاً في المكتبة
تحديد معدل الطلبات في C# (.NET): دليل مكتبة System.Threading.RateLimiting
دليل Apache POI الكامل لجافا: أمثلة عملية
العمل مع الصور في PHP: تغيير الحجم (resize) والقص (crop) والتحسين باستخدام GD وImagick
مكتبة Go القياسية (Golang): نظرة شاملة