مقدمة: SOLID و Rust - مزيج مثالي
\nمبادئ SOLID هي قواعد أساسية للتصميم الموجه للكائنات تساعد في إنشاء كود مرن وقابل للصيانة والتوسع. على الرغم من أن Rust ليست لغة موجهة للكائنات بشكل خالص (فهي تستخدم traits والهياكل بدلاً من الفئات)، إلا أن مبادئ SOLID قابلة للتطبيق عليها بشكل كامل. في هذه المقالة، سنشرح كل مبدأ من المبادئ الخمسة مع أمثلة كود بلغة Rust، موضحين كيف تساعد في بناء أنظمة موثوقة.
\nخلال المقالة، سترى كيف تجعل Rust مع نظام الملكية (ownership) و traits والأدوية (generics) تطبيق SOLID طبيعيًا وقويًا. سنبدأ بمبدأ المسؤولية الواحدة وننتهي بمبدأ عكس التبعية، مع تقديم أمثلة جاهزة للاستخدام.
\n\n1. مبدأ المسؤولية الواحدة (SRP)
\nالجوهر: يجب أن يكون لكل وحدة أو فئة أو دالة سبب واحد فقط للتغيير. في Rust، يعني هذا أن الهيكل يجب أن يكون مسؤولاً عن مهمة واحدة.
\n\nمثال سيء (انتهاك SRP)
\nstruct Report {\n title: String,\n content: String,\n}\n\nimpl Report {\n fn generate(&self) -> String {\n format!("# {}\\n\\n{}", self.title, self.content)\n }\n\n fn save_to_file(&self, path: &str) -> std::io::Result<()> {\n std::fs::write(path, self.generate())?;\n Ok(())\n }\n\n fn send_email(&self, recipient: &str) {\n println!("Sending email to {} with report", recipient);\n }\n}\nهنا، الهيكل Report مسؤول عن التوليد والحفظ والإرسال - ثلاثة أسباب مختلفة للتغيير. هذا ينتهك SRP.
مثال جيد (الالتزام بـ SRP)
\nstruct Report {\n title: String,\n content: String,\n}\n\nimpl Report {\n fn generate(&self) -> String {\n format!("# {}\\n\\n{}", self.title, self.content)\n }\n}\n\nstruct ReportSaver;\n\nimpl ReportSaver {\n fn save(&self, report: &Report, path: &str) -> std::io::Result<()> {\n std::fs::write(path, report.generate())?;\n Ok(())\n }\n}\n\nstruct EmailSender;\n\nimpl EmailSender {\n fn send(&self, recipient: &str, message: &str) {\n println!("Sending email to {}: {}", recipient, message);\n }\n}\nالآن، كل هيكل مسؤول عن مهمته الخاصة: Report للبيانات والتوليد، ReportSaver للحفظ، EmailSender للإرسال. هذا يبسط الاختبار والصيانة.
2. مبدأ الفتح/الإغلاق (OCP)
\nالجوهر: يجب أن تكون الكيانات البرمجية مفتوحة للتمديد ولكن مغلقة للتعديل. في Rust، يتم تحقيق ذلك من خلال traits والأدوية (generics).
\n\nمثال: نظام التصفية
\ntrait Filter<T> {\n fn apply(&self, items: &[T]) -> Vec<T>;\n}\n\nstruct EvenFilter;\n\nimpl Filter<i32> for EvenFilter {\n fn apply(&self, items: &[i32]) -> Vec<i32> {\n items.iter().filter(|&x| x % 2 == 0).copied().collect()\n }\n}\n\nstruct GreaterThanFilter {\n threshold: i32,\n}\n\nimpl Filter<i32> for GreaterThanFilter {\n fn apply(&self, items: &[i32]) -> Vec<i32> {\n items.iter().filter(|&&x| x > self.threshold).copied().collect()\n }\n}\n\nfn process<T: Clone, F: Filter<T>>(items: &[T], filter: F) -> Vec<T> {\n filter.apply(items)\n}\n\nfn main() {\n let numbers = vec![1, 2, 3, 4, 5, 6];\n let even = process(&numbers, EvenFilter);\n let greater = process(&numbers, GreaterThanFilter { threshold: 3 });\n println!("Even: {:?}, Greater: {:?}", even, greater);\n}\nيمكننا إضافة مرشحات جديدة دون تعديل الكود الموجود process. هذا هو تطبيق OCP من خلال تعدد الأشكال (polymorphism) باستخدام traits.
3. مبدأ استبدال ليسكوف (LSP)
\nالجوهر:
المدونات
توصيات الكتب
اقرأ أيضًا