حقن التبعية في Rust: عملياً

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

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

ابدأ الدورة

حقن التبعية في Rust: عملياً

\n\n

حقن التبعية (DI) هو نمط تصميم يسمح بتمرير التبعيات إلى الكائن من الخارج، بدلاً من إنشائها داخله. في Rust، هذا النهج مفيد بشكل خاص بفضل نظام الملكية، Traits، والتعميمات. في هذه المقالة، سنستعرض كيفية تنفيذ DI في Rust عملياً، من الأمثلة البسيطة إلى التقنيات المتقدمة مع حاويات التبعية.

\n\n

لماذا DI مهم في Rust؟

\n

في Rust، حيث لا يوجد برمجة كائنية تقليدية مع الوراثة، يساعد DI في:

\n
    \n
  • تقليل الاقتران — تعتمد الوحدات على التجريدات، وليس على التطبيقات الملموسة.
  • \n
  • تبسيط الاختبار — يمكنك استبدال التطبيقات الحقيقية بـ Mocks.
  • \n
  • زيادة المرونة — سهولة تغيير سلوك النظام دون تعديل كوده.
  • \n
\n\n

الأساسيات: تمرير التبعيات عبر المعاملات

\n

أبسط طريقة لحقن التبعية هي تمريرها كمعامل لدالة أو مُنشئ هيكل.

\n\n
// تعريف Trait للتجريد\ntrait Logger {\n    fn log(&self, message: &str);\n}\n\n// تطبيق للكونسول\nstruct ConsoleLogger;\n\nimpl Logger for ConsoleLogger {\n    fn log(&self, message: &str) {\n        println!("LOG: {}", message);\n    }\n}\n\n// تطبيق للملف\nstruct FileLogger {\n    filename: String,\n}\n\nimpl Logger for FileLogger {\n    fn log(&self, message: &str) {\n        // في الكود الحقيقي، سيتم الكتابة إلى ملف\n        println!("FILE: {} -> {}", self.filename, message);\n    }\n}\n\n// خدمة تستخدم DI عبر المُنشئ\nstruct UserService<L: Logger> {\n    logger: L,\n}\n\nimpl<L: Logger> UserService<L> {\n    fn new(logger: L) -> Self {\n        Self { logger }\n    }\n\n    fn create_user(&self, name: &str) {\n        self.logger.log(&format!("User created: {}", name));\n    }\n}\n\nfn main() {\n    let console_logger = ConsoleLogger;\n    let user_service = UserService::new(console_logger);\n    user_service.create_user("Alice");\n}\n
\n\n

في هذا المثال، UserService لا يعرف أي Logger محدد يُستخدم — فهو يعتمد فقط على Trait Logger. هذا هو جوهر DI.

\n\n

نهج متقدم: كائنات Traits والتوزيع الديناميكي

\n

عندما تحتاج لتخزين تطبيقات مختلفة في حاوية واحدة، استخدم كائنات Traits (Box<dyn Trait>).

\n\n
trait Database {\n    fn query(&self, sql: &str) -> Vec<String>;\n}\n\nstruct PostgresDb;\nimpl Database for PostgresDb {\n    fn query(&self, sql: &str) -> Vec<String> {\n        vec!["result1".to_string(), "result2".to_string()]\n    }\n}\n\nstruct SqliteDb;\nimpl Database for SqliteDb {\n    fn query(&self, sql: &str) -> Vec<String> {\n        vec!["sqlite_result".to_string()]\n    }\n}\n\nstruct App {\n    db: Box<dyn Database>,\n    logger: Box<dyn Logger>,\n}\n\nimpl App {\n    fn new(db: Box<dyn Database>, logger: Box<dyn Logger>) -> Self {\n        Self { db, logger }\n    }\n\n    fn run(&self) {\n        let results = self.db.query("SELECT * FROM users");\n        for r in results {\n            self.logger.log(&r);\n        }\n    }\n}\n\nfn main() {\n    let app = App::new(\n        Box::new(PostgresDb),\n        Box::new(ConsoleLogger),\n    );\n    app.run();\n}\n
\n\n

ميزة كائنات Traits هي أنه يمكنك تغيير التطبيقات أثناء وقت التشغيل. العيب هو الحمل الزائد البسيط للتوزيع الديناميكي.

\n\n

حاوية التبعية: عكس التحكم

\n

للمشاريع الكبيرة، من المناسب استخدام حاوية التبعية. على الرغم من عدم وجود حاوية DI قياسية في Rust، إلا أنه من السهل تنفيذها بنفسك أو استخدام Crates مثل shaku أو dice.

\n\n

مثال على حاوية بسيطة يدوياً:

\n\n
use std::collections::HashMap;\nuse std::any::Any;\n\n// Ty                

المدونات

توصيات الكتب