Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

لماذا لا تدعم Go الوراثة

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 34 من 107.

تستخدم لغات البرمجة كائنية التوجه التقليدية مثل Java و C++ الوراثة لمشاركة الكود بين الأنواع. حيث تقوم الفئة الابنة (child class) بتوسيع الفئة الأب (parent class)، لترث جميع حقولها ودوالها. وتتجاهل لغة Go هذه الميزة عمدًا.

تخلق الوراثة ارتباطاً وثيقاً بين الأنواع. عندما تتغير الفئة الأب (parent class)، تتأثر جميع الفئات الأبناء (child classes).

تصبح التسلسلات الهرمية العميقة للوراثة صعبة الفهم والصيانة. تحدث "مشكلة الفئة الأساسية الهشة" (fragile base class problem) عندما تؤدي التعديلات على الفئة الأساسية إلى كسر الفئات المشتقة بشكل غير متوقع.

اختار مصممو Go مساراً مختلفاً: التركيب بدلاً من الوراثة (composition over inheritance). بدلاً من قول "إن Dog هو Animal"، تشجعك Go على قول "إن Dog لديه سلوكيات تشبه Animal". يؤدي هذا التحول البسيط إلى كود أكثر مرونة وقابلية للصيانة.

يحقق Go إعادة استخدام الكود من خلال آليتين تعلمتهما بالفعل:

  • الواجهات (Interfaces) تُحدد عقود السلوك بدون تفاصيل التنفيذ
  • تضمين الـ Struct يسمح للأنواع بتضمين أنواع أخرى وإعادة استخدام الـ methods الخاصة بها

تأمل هذه المقارنة. في البرمجة كائنية التوجه (OOP) التقليدية، قد تكتب class Dog extends Animal. في لغة Go، تقوم بتضمين هيكل Animal داخل Dog وتنفذ واجهات (interfaces) مشتركة. والنتيجة هي وظائف مماثلة مع اقتران أقل بين الأنواع.

يستكشف هذا الفصل مفهوم تضمين الهياكل (struct embedding) بعمق، موضحاً كيف تحقق لغة Go فوائد الوراثة (inheritance) دون عيوبها.

challenge icon

التحدي

سهل

لنقم ببناء نظام إشعارات يوضح نهج التركيب (composition) في لغة Go بدلاً من الوراثة (inheritance). ستقوم بإنشاء أنواع تشترك في السلوك من خلال الواجهات (interfaces) وتضمين الهياكل (struct embedding) بدلاً من التسلسلات الهرمية للفئات.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • notifier.go: قم بتعريف واجهة Notifier مع طريقة واحدة Notify(message string) string. قم أيضاً بإنشاء هيكل BaseNotifier مع حقل Name سيتم تضمينه بواسطة أنواع أخرى. امنح BaseNotifier طريقة تسمى Format(message string) string تعيد الرسالة مسبوقة باسم المنبه بين قوسين مربعيين.
  • channels.go: قم بإنشاء نوعين من قنوات الإشعارات يقومان بتضمين BaseNotifier وينفذان واجهة Notifier:
    • EmailNotifier مع حقل إضافي Address
    • SMSNotifier مع حقل إضافي Phone
    يجب أن تستخدم طريقة Notify لكل نوع طريقة Format المضمنة وتتضمن معلومات القناة الخاصة بها في المخرجات.
  • main.go: قم بإنشاء دالة تسمى SendAlert تقبل أي Notifier ورسالة، ثم تعيد نتيجة استدعاء Notify. اقرأ تفاصيل الإشعارات من المدخلات، وأنشئ كلا النوعين من المنبهات، ووضح كيف يمكن استخدامهما بالتبادل من خلال الواجهة.

سيتم توفير المدخلات التالية:

  • السطر 1: اسم المنبه للبريد الإلكتروني
  • السطر 2: عنوان البريد الإلكتروني
  • السطر 3: اسم المنبه للرسائل القصيرة (SMS)
  • السطر 4: رقم الهاتف
  • السطر 5: رسالة التنبيه

يجب أن تعيد طريقة Format في BaseNotifier ما يلي:

[Name] message

يجب أن تعيد طرق Notify ما يلي:

  • EmailNotifier: Email to [Address]: [formatted message]
  • SMSNotifier: SMS to [Phone]: [formatted message]

على سبيل المثال، إذا كانت المدخلات Alerts، و user@mail.com، و Urgent، و 555-1234، و Server down، يجب أن تكون مخرجاتك:

Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down

لاحظ كيف يعيد كلا نوعي المنبهات استخدام طريقة Format من BaseNotifier من خلال التضمين، بينما يوفر كل منهما تنفيذ Notify الخاص به. تعمل دالة SendAlert مع أي Notifier دون معرفة النوع الملموس—هذا هو مبدأ التركيب بدلاً من الوراثة في الواقع العملي.

ورقة مرجعية

تستخدم لغة Go التركيب بدلاً من الوراثة (composition over inheritance) لمشاركة الكود بين الأنواع، متجنبةً بذلك مشاكل الارتباط الوثيق والفئة الأساسية الهشة التي تظهر في الوراثة التقليدية الموجهة للكائنات.

بدلاً من التسلسلات الهرمية للفئات، تحقق Go إعادة استخدام الكود من خلال:

  • الواجهات (Interfaces) - تحدد عقود السلوك دون تفاصيل التنفيذ
  • تضمين الهياكل (Struct embedding) - يسمح للأنواع بتضمين أنواع أخرى وإعادة استخدام دوالها (methods)

بدلاً من قول "الكلب هو حيوان" (الوراثة)، تشجع Go على قول "الكلب لديه سلوكيات تشبه الحيوانات" (التركيب).

مثال على تضمين الهياكل (Struct Embedding)

تعريف هيكل أساسي سيتم تضمينه:

type BaseNotifier struct {
    Name string
}

func (b BaseNotifier) Format(message string) string {
    return "[" + b.Name + "] " + message
}

تضمين الهيكل الأساسي في أنواع أخرى لإعادة استخدام حقوله ودواله:

type EmailNotifier struct {
    BaseNotifier  // هيكل مضمن
    Address string
}

func (e EmailNotifier) Notify(message string) string {
    // يمكن استدعاء دالة Format المضمنة مباشرة
    return "Email to " + e.Address + ": " + e.Format(message)
}

تعريف واجهة (interface) يمكن لعدة أنواع تنفيذها:

type Notifier interface {
    Notify(message string) string
}

استخدام الواجهة للعمل مع أنواع ملموسة مختلفة بشكل تبادلي:

func SendAlert(n Notifier, message string) string {
    return n.Notify(message)
}

يوفر هذا النهج وظائف مشابهة للوراثة ولكن مع ارتباط أقل وثوقاً بين الأنواع.

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
)

// SendAlert تقبل أي Notifier ورسالة، وتعيد نتيجة استدعاء Notify
// TODO: قم بتنفيذ دالة SendAlert

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// قراءة اسم مرسل البريد الإلكتروني
	scanner.Scan()
	emailName := scanner.Text()
	
	// قراءة عنوان البريد الإلكتروني
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// قراءة اسم مرسل الرسائل النصية SMS
	scanner.Scan()
	smsName := scanner.Text()
	
	// قراءة رقم الهاتف
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// قراءة رسالة التنبيه
	scanner.Scan()
	alertMessage := scanner.Text()
	
	// TODO: قم بإنشاء EmailNotifier باستخدام emailName و emailAddress
	
	// TODO: قم بإنشاء SMSNotifier باستخدام smsName و phoneNumber
	
	// TODO: استخدم SendAlert لإرسال alertMessage عبر كلا المرسلين
	// وقم بطباعة النتائج
	
	// كتم تحذيرات المتغيرات غير المستخدمة (قم بإزالتها عند استخدام المتغيرات)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming