مقدمة إلى Dependency Injection في Rust
Dependency Injection (DI) هو نمط تصميم يحصل فيه الكائن على تبعياته من الخارج، بدلاً من إنشائها بنفسه. في Rust، حيث تأتي الأمان والأداء في المقام الأول، يساعد DI في إنشاء كود مرن وقابل للاختبار ومنخفض الاقتران. في هذه المقالة، سنشرح كيفية تنفيذ حقن التبعيات في Rust باستخدام traits والأدوية الجنيسة (generics) والحاويات (containers).
على عكس اللغات التي تحتوي على انعكاس (reflection) مثل Java و C#، لا يوفر Rust أطر عمل DI مدمجة. ومع ذلك، فإن نظام الأنواع والملكية الخاص به يسمح بإنشاء حلول أنيقة دون فقدان الأداء.
الأساليب الأساسية لـ Dependency Injection
توجد عدة طرق لتنفيذ DI في Rust. سننظر في ثلاثة أساليب رئيسية.
1. الحقن عبر المُنشئ (Constructor Injection)
الطريقة الأبسط والأكثر شيوعًا هي تمرير التبعيات إلى مُنشئ الهيكل (struct). وهذا يضمن أن الكائن يكون دائمًا في حالة صالحة.
// تعريف trait لإرسال الإشعاراتtrait Notifier { fn send(&self, message: &str);}
// تنفيذ للبريد الإلكترونيstruct EmailNotifier { smtp_server: String,}
impl Notifier for EmailNotifier { fn send(&self, message: &str) { println!("إرسال بريد إلكتروني عبر {}: {}", self.smtp_server, message); }}
// تنفيذ للرسائل النصيةstruct SmsNotifier { api_key: String,}
impl Notifier for SmsNotifier { fn send(&self, message: &str) { println!("إرسال رسالة نصية بالمفتاح {}: {}", self.api_key, message); }}
// خدمة تستخدم Notifierstruct NotificationService { notifier: Box<dyn Notifier>,}
impl NotificationService { // حقن التبعية عبر المُنشئ fn new(notifier: Box<dyn Notifier>) -> Self { NotificationService { notifier } }
fn notify(&self, message: &str) { self.notifier.send(message); }}
fn main() { let email_notifier = Box::new(EmailNotifier { smtp_server: "smtp.example.com".to_string(), }); let service = NotificationService::new(email_notifier); service.notify("Hello, Rust!");}2. الحقن عبر الأدوية الجنيسة (Generic Injection)
يسمح استخدام الأدوية الجنيسة (generics) بتجنب الإرسال الديناميكي (dynamic dispatch) وزيادة الأداء من خلال التمثيل الأحادي (monomorphization).
// نفس trait Notifier، لكن الخدمة الآن معممةstruct NotificationService<T: Notifier> { notifier: T,}
impl<T: Notifier> NotificationService<T> { fn new(notifier: T) -> Self { NotificationService { notifier } }
fn notify(&self, message: &str) { self.notifier.send(message); }}
fn main() { let email_notifier = EmailNotifier { smtp_server: "smtp.example.com".to_string(), }; let service = NotificationService::new(email_notifier); service.notify("Hello from generic Rust!");}3. الحقن عبر حاوية التبعيات (Dependency Container)
للمشاريع المعقدة، من المفيد استخدام حاوية تبعيات تدير إنشاء الكائنات وعمرها. في Rust، يمكن تنفيذ حاوية بسيطة يدويًا.
use std::collections::HashMap;
// trait لمصانع التبعياتtrait Factory<T> { fn create(&self) -> T;}
// حاوية تبعيات بسيطةstruct Container { factories: HashMap<String, Box<dyn std::any::Any>>,}
impl Container { fn new() -> Self { Container { factories: HashMap::new(), } }
fn register<T: 'static>(&mut self, name: &str, factory: Box<dyn Factory<T>>) { self.factories.insert(name.to_string(), Box::new(factory)); }
fn resolve<T: 'static>(&self, name: &str) -> Option<T> { self.factories.get(name).and_then(|f|