مقدمة: لماذا SOLID مهم لمطوري Rust
SOLID هي خمسة مبادئ أساسية للتصميم الكائني التوجه تساعد في إنشاء كود مرن وقابل للصيانة والتوسع. على الرغم من أن Rust ليست لغة OOP كلاسيكية، إلا أن هذه المبادئ قابلة للتطبيق بشكل ممتاز بفضل نظام الـ traits والملكية والأدوية (Generics). في هذه المقالة، سنشرح كل مبدأ من مبادئ SOLID بأمثلة عملية بلغة Rust.
ستتعلم كيف:
- تجنب الهياكل "الإلهية" باستخدام التحليل (decomposition)
- تصميم أنظمة قابلة للتوسع من خلال الـ traits
- استخدام تعدد الأشكال (polymorphism) بكفاءة دون وراثة
- فصل الواجهات (interfaces) وحقن التبعيات (dependency injection)
1. مبدأ المسؤولية الواحدة (Single Responsibility Principle - SRP)
ينص مبدأ SRP على: "يجب أن يكون للهيكل أو الوحدة سبب واحد فقط للتغيير". في Rust، هذا يعني أن كل نوع يجب أن يكون مسؤولاً عن مهمة واحدة محددة بوضوح.
انتهاك مبدأ SRP: الهيكل "الإلهي"
struct Report { data: Vec<String>,}
impl Report { fn generate(&self) -> String { self.data.join(", ") }
fn save_to_file(&self, path: &str) -> std::io::Result<()> { let content = self.generate(); std::fs::write(path, content) }
fn send_email(&self, recipient: &str) { // منطق إرسال البريد الإلكتروني println!("إرسال التقرير {} إلى {}", self.generate(), recipient); }}هذا الكود ينتهك مبدأ SRP: الهيكل Report مسؤول عن توليد البيانات، وحفظها في ملف، وإرسالها عبر البريد الإلكتروني. أي تغيير في منطق الحفظ أو الإرسال سيتطلب تعديل التقرير نفسه.
التنفيذ الصحيح مع مبدأ SRP
struct Report { data: Vec<String>,}
impl Report { fn new(data: Vec<String>) -> Self { Self { data } }
fn generate(&self) -> String { self.data.join(", ") }}
struct FileSaver;
impl FileSaver { fn save(report: &Report, path: &str) -> std::io::Result<()> { std::fs::write(path, report.generate()) }}
struct EmailSender;
impl EmailSender { fn send(report: &Report, recipient: &str) { println!("إرسال التقرير {} إلى {}", report.generate(), recipient); }}الآن كل هيكل لديه مسؤولية واحدة: Report للتوليد، FileSaver للحفظ، EmailSender للإرسال. هذا يبسط اختبار الكود وتعديله.
2. مبدأ الانفتاح/الانغلاق (Open/Closed Principle - OCP)
ينص مبدأ OCP على: "يجب أن تكون الفئات مفتوحة للتمديد ولكن مغلقة للتعديل". في Rust، يتم تحقيق ذلك من خلال الـ traits والأدوية (Generics).
مثال: نظام معالجة المدفوعات
trait PaymentProcessor { fn process(&self, amount: f64) -> String;}
struct CreditCardProcessor;
impl PaymentProcessor for CreditCardProcessor { fn process(&self, amount: f64) -> String { format!("معالجة {} عبر بطاقة الائتمان", amount) }}
struct PayPalProcessor;
impl PaymentProcessor for PayPalProcessor { fn process(&self, amount: f64) -> String { format!("معالجة {} عبر PayPal", amount) }}
struct PaymentHandler<T: PaymentProcessor> { processor: T,}
impl<T: PaymentProcessor> PaymentHandler<T> { fn execute(&self, amount: f64) -> String { self.processor.process(amount) }}إضافة طريقة دفع جديدة (مثل CryptoProcessor) لا تتطلب تعديل الكود الموجود - يكفي تنفيذ trait PaymentProcessor.