مبادئ SOLID في Go: النظرية والتطبيق
SOLID هو اختصار لخمسة مبادئ أساسية في التصميم كائني التوجيه، والتي تساعد في إنشاء كود مرن وقابل للصيانة والتوسع. على الرغم من أن Go ليست لغة OOP كلاسيكية (لا تحتوي على وراثة الفئات)، إلا أن واجهاتها (interfaces) وتكوينها (composition) وكتابتها الصارمة (strict typing) تسمح بتطبيق SOLID بنجاح. في هذه المقالة، سنشرح كل مبدأ مع أمثلة من العالم الحقيقي بلغة Go.
1. مبدأ المسؤولية الواحدة (SRP — Single Responsibility Principle)
النظرية: يجب أن يكون لكل نوع (هيكل، دالة، واجهة) سبب واحد فقط للتغيير. بمعنى آخر، هو مسؤول فقط عن جزء واحد من وظائف البرنامج.
التطبيق في Go: الخطأ الشائع هو إنشاء هياكل «شاملة» (god objects) تهتم بمنطق الأعمال والتسجيل وإرسال الإشعارات في آن واحد.
مثال سيء (انتهاك SRP):
type OrderService struct{}
func (s *OrderService) Create(order Order) error { // 1. الحفظ في قاعدة البيانات // 2. إرسال البريد الإلكتروني // 3. التسجيل (Logging) return nil}مثال جيد (الالتزام بـ SRP):
type OrderRepository interface { Save(order Order) error}
type EmailService interface { Send(to string, subject string, body string) error}
type Logger interface { Info(msg string)}
type OrderService struct { repo OrderRepository email EmailService log Logger}
func (s *OrderService) Create(order Order) error { if err := s.repo.Save(order); err != nil { s.log.Info("Failed to save order") return err } if err := s.email.Send(order.Email, "Order Confirmed", "Your order is placed"); err != nil { s.log.Info("Failed to send email") } s.log.Info("Order created successfully") return nil}الآن كل هيكل مسؤول فقط عن مهمته الخاصة، ولن يؤثر تغيير منطق البريد الإلكتروني على حفظ الطلبات.
2. مبدأ الانفتاح/الإغلاق (OCP — Open/Closed Principle)
النظرية: يجب أن تكون الفئات (الأنواع) مفتوحة للتمديد (extension) ولكن مغلقة للتعديل (modification). أي يجب أن تكون قادراً على إضافة وظائف جديدة دون تغيير الكود الموجود.
التطبيق في Go: استخدم الواجهات (interfaces) والتكوين (composition) لإضافة تطبيقات جديدة دون تغيير المنطق الأساسي.
مثال: لنفترض أن لدينا نظاماً لحساب تكلفة الشحن. في البداية يوجد فقط «شحن عادي»، لكننا نحتاج لإضافة «شحن سريع».
// تعريف الواجهةtype ShippingCostCalculator interface { Calculate(order Order) float64}
// تطبيق قياسيtype StandardShipping struct{}
func (s StandardShipping) Calculate(order Order) float64 { return 5.99}
// تطبيق جديد دون تغيير الكود أعلاهtype ExpressShipping struct{}
func (e ExpressShipping) Calculate(order Order) float64 { return 15.99}
// الاستخدامfunc ProcessOrder(order Order, calculator ShippingCostCalculator) { cost := calculator.Calculate(order) fmt.Printf("Shipping cost: %.2f", cost)}نحن لا نغير ProcessOrder — فقط نمرر نسخة جديدة من ExpressShipping. هذا هو مبدأ OCP.
3. مبدأ استبدال ليسكوف (LSP — Liskov Substitution Principle)
النظرية: يجب أن تكون الكائنات في البرنامج قابلة للاستبدال بنسخ من أنواعها الفرعية (subtypes) دون التأثير على صحة البرنامج. في Go، هذا يعني أنه إذا كانت الدالة تتوقع واجهة (interface)، فيجب أن يعمل أي تطبيق (implementation) لهذه الواجهة بشكل صحيح.
التطبيق في Go: تأكد من أن تطبيقاتك للواجهات لا تخالف توقعات الكود المستدعي. على سبيل المثال، إذا كانت الواجهة تفترض أن الدالة GetName() تعود دائماً