تخطَّ إلى المحتوى

🐹 شرح Go

الدوال على الأنواع (Methods)

الدرس 12 من 30· ⏱ 3 دقائق قراءة· 🗓 آخر تحديث: ١٨ أكتوبر ٢٠٢٦

ما هي الـ Method؟

Go لا تملك classes، لكن تقدر تربط دوال بأي نوع تعرّفه. الـ method دالة عادية لها مُستقبِل (receiver) مكتوب قبل اسمها:

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func main() {
    rect := Rectangle{Width: 4, Height: 3}
    fmt.Println(rect.Area())   // 12
}

(r Rectangle) تعني: هذه الدالة تعمل على قيمة من نوع Rectangle اسمها داخل الدالة r. الاصطلاح في Go اسم قصير من حرف أو حرفين (r وu وs)، لا this أو self.

المُستقبِل بالقيمة: نسخة

func (r Rectangle) TryScale(f float64) {
    r.Width *= f    // تعدّل النسخة فقط
}

func main() {
    rect := Rectangle{Width: 4, Height: 3}
    rect.TryScale(2)
    fmt.Println(rect.Width)   // 4: لم يتغيّر!
}

المُستقبِل يُعامل كأي معامل: يُمرَّر بالقيمة. هذا أشهر خطأ مع الـ methods في Go: method "تعدّل" ولا يتغيّر شيء.

المُستقبِل بالمؤشّر: يعدّل الأصل

func (r *Rectangle) Scale(f float64) {
    r.Width *= f
    r.Height *= f
}

func main() {
    rect := Rectangle{Width: 4, Height: 3}
    rect.Scale(2)            // Go تكتب (&rect).Scale(2) تلقائيًا
    fmt.Println(rect.Width)  // 8
}

لا تحتاج كتابة &rect: إذا كان المتغيّر قابلًا لأخذ عنوانه، تفعل Go ذلك تلقائيًا عند استدعاء method بمُستقبِل مؤشّر.

متى أستخدم كلًّا؟

المُستقبِلمتى
بالمؤشّر (r *T)الـ method تعدّل الحالة، أو النوع كبير، أو يحتوي sync.Mutex (لا يجوز نسخه)
بالقيمة (r T)قراءة فقط لنوع صغير، أو نوع يُعامل كقيمة ثابتة مثل time.Time

والاصطلاح: إذا احتاجت method واحدة مُستقبِلًا بالمؤشّر، اجعل كل methods النوع بالمؤشّر. الخلط يربك القارئ، ويؤثّر على أي الأنواع تحقّق الواجهات كما في الدرس التالي.

مثال عملي: حساب بنكي

type Account struct {
    Owner   string
    balance float64   // حرف صغير: غير مُصدَّر، لا يُعدَّل إلا عبر الـ methods
}

func (a *Account) Deposit(amount float64) {
    if amount > 0 {
        a.balance += amount
    }
}

func (a *Account) Withdraw(amount float64) error {
    if amount > a.balance {
        return fmt.Errorf("رصيد غير كافٍ: المتاح %.2f", a.balance)
    }
    a.balance -= amount
    return nil
}

func (a *Account) Balance() float64 { return a.balance }

func main() {
    acc := &Account{Owner: "سارة"}
    acc.Deposit(100)
    if err := acc.Withdraw(150); err != nil {
        fmt.Println(err)          // رصيد غير كافٍ: المتاح 100.00
    }
    fmt.Println(acc.Balance())    // 100
}

الحقل balance يبدأ بحرف صغير، فلا تصل إليه الحزم الأخرى مباشرة، والطريقة الوحيدة لتغييره هي Deposit وWithdraw التي تفرض القواعد. هذا هو "التغليف" (encapsulation) في Go: على مستوى الحزمة لا الكلاس.

Methods على أي نوع تعرّفه

ليست للبِنى فقط. أي نوع جديد في حزمتك يقبل methods:

type Celsius float64

func (c Celsius) ToFahrenheit() float64 {
    return float64(c)*9/5 + 32
}

func main() {
    t := Celsius(25)
    fmt.Println(t.ToFahrenheit())   // 77
}

لكن لا يمكنك إضافة methods لنوع من حزمة أخرى (مثل int أو time.Time) مباشرة؛ عرّف نوعًا جديدًا مبنيًا عليه كما فعلنا مع Celsius.

أخطاء شائعة

  • مُستقبِل بالقيمة في method تعدّل: التعديل يضيع. استخدم *T.
  • خلط مُستقبِلات القيمة والمؤشّر في نفس النوع: اختر واحدًا للنوع كله.
  • نسخ struct فيه sync.Mutex عبر مُستقبِل بالقيمة: كل نسخة لها قفل مستقل، فلا حماية. go vet يحذّرك (copylocks).
  • استدعاء method بمُستقبِل مؤشّر على مؤشّر nil: يعمل الاستدعاء، لكن أي وصول لحقل داخلها يسبّب panic.

🎯 التالي: الواجهات (Interfaces).

شرح الدوال على الأنواع (Methods) — Go بالعربي
الدوال على الأنواع (Methods) — Go بالعربي · The Code Fix

📚 لمزيد من التعمّق في Go، راجِع التوثيق الرسمي لـ Go.

هل كان هذا الدرس مفيدًا؟