مقدمة: لماذا SOLID مهم في Go؟
SOLID هي خمسة مبادئ أساسية للبرمجة كائنية التوجه تساعد في إنشاء كود مرن وقابل للصيانة والتوسع. على الرغم من أن Go ليست لغة OOP كلاسيكية (لا تحتوي على كلاسات أو وراثة)، إلا أن هذه المبادئ قابلة للتطبيق بشكل ممتاز بفضل الـ interfaces والـ composition والـ strict typing. في هذه المقالة، سنشرح كل مبدأ من مبادئ SOLID مع أمثلة بلغة Go، لتتمكن من كتابة كود أفضل وأكثر قابلية للتوسع.
1. Single Responsibility Principle (SRP) — مبدأ المسؤولية الواحدة
ينص SRP على: "يجب أن يكون للوحدة سبب واحد فقط للتغيير". في Go، هذا يعني أن الدالة أو الـ struct أو الـ package يجب أن تكون مسؤولة عن مهمة واحدة محددة بوضوح.
مثال سيء (انتهاك SRP):
type Report struct { Title string Body string}
func (r *Report) Generate() string { return fmt.Sprintf("<h1>%s</h1><p>%s</p>", r.Title, r.Body)}
func (r *Report) SaveToFile(filename string) error { return os.WriteFile(filename, []byte(r.Generate()), 0644)}
func (r *Report) SendEmail(addr string) error { // منطق إرسال البريد الإلكتروني return nil}هنا، الـ struct Report مسؤولة عن التوليد والحفظ والإرسال — ثلاثة أسباب مختلفة للتغيير.
مثال جيد (الالتزام بـ SRP):
type Report struct { Title string Body string}
func (r *Report) Generate() string { return fmt.Sprintf("<h1>%s</h1><p>%s</p>", r.Title, r.Body)}
type ReportSaver struct{}
func (rs *ReportSaver) SaveToFile(report *Report, filename string) error { return os.WriteFile(filename, []byte(report.Generate()), 0644)}
type EmailSender struct{}
func (es *EmailSender) Send(report *Report, addr string) error { // منطق إرسال البريد الإلكتروني return nil}الآن كل كيان له مسؤولية واحدة: التوليد والحفظ والإرسال.
2. Open/Closed Principle (OCP) — مبدأ الانفتاح/الإغلاق
ينص OCP على: "يجب أن تكون الكيانات البرمجية مفتوحة للتمديد ولكن مغلقة للتعديل". في Go، يتم تحقيق ذلك من خلال الـ interfaces والـ composition.
مثال سيء (انتهاك OCP):
type PaymentProcessor struct{}
func (p *PaymentProcessor) Process(paymentType string, amount float64) { if paymentType == "credit_card" { // معالجة بطاقة الائتمان } else if paymentType == "paypal" { // معالجة PayPal } else if paymentType == "crypto" { // معالجة العملات الرقمية }}عند إضافة نوع دفع جديد، سيتعين تعديل الدالة Process.
مثال جيد (الالتزام بـ OCP):
type PaymentMethod interface { Pay(amount float64) error}
type CreditCard struct{}func (c *CreditCard) Pay(amount float64) error { // منطق الدفع بالبطاقة return nil}
type PayPal struct{}func (p *PayPal) Pay(amount float64) error { // منطق الدفع عبر PayPal return nil}
type PaymentProcessor struct{}
func (pp *PaymentProcessor) Process(method PaymentMethod, amount float64) error { return method.Pay(amount)}الآن، لإضافة طريقة دفع جديدة، يكفي تنفيذ الـ interface PaymentMethod دون لمس الكود الموجود.
3. Liskov Substitution Principle (LSP) — مبدأ استبدال باربرا ليكوف
يتطلب LSP: "يجب أن تكون الكائنات في البرنامج قابلة للاستبدال بنسخ من أنواعها الفرعية دون تغيير صحة البرنامج". في Go، يتعلق هذا بتنفيذ الـ interfaces.
مثال سيء (انتهاك LSP):
type Bird interface { Fly() error}
type Penguin struct{}func (p *Penguin) Fly() error { return errors.New("طيور البطريق لا تطير")}
func LetBirdFly(b Bird) {
```