الأساسيات

دوال الأساليب Methods

سبق أن تعلمنا عن الدوال في لغة غو, ولكن هنالك نوع خاص من الدوال تُدعى “الأساليب” او بالانكليزي Methods ميثود, وسنتعرف عليها أكثر في هذا الدرس ..

الميثود هي عبارة عن دالة مع مُتلقي Reciever .

والمُتلقي يكون له اسم ونوع, ويكون موضعه مباشرة بعد الكلمة المفتاحية func وقبل اسم الدالة, كالصيغة التالية

func (receiver Type) MethodName(parameterList) (returnTypes) {
}

المتلقي يمكن أن يكون لأي نوع بيانات أساسي أو بنية struct .

مثال يوضح الميثود في لغة غو

package main

import (
	"fmt"
)

// Struct type - `Point`
type Point struct {
	X, Y float64
}

// Method with receiver `Point`
func (p Point) IsAbove(y float64) bool {
	return p.Y > y
}

func main() {
	p := Point{2.0, 4.0}

	fmt.Println("Point : ", p)

	fmt.Println("Is Point p located above the line y = 1.0 ? : ", p.IsAbove(1))
}

ونتيجة تنفيذ البرنامج تكون

Point :  {2 4}
Is Point p located above the line y = 1.0 ? :  true

لاحظ أننا استدعينا الميثود IsAbove على متغير p من نوع بنية Point, وهي تماماً الطريقة التي نستدعي بها الميثود في لغات البرمجة التي تدعم البرمجة كائنية التوجه OOP.

– الميثود هو دالة

وبما أن الأساليب (الميثود) هي مجرد دوال لها متلقي, يمكننا كتابة المثال السابق ولكن كدالة عادية كالتالي:

package main

import (
	"fmt"
)

// Struct type - `Point`
type Point struct {
	X, Y float64
}

func IsAboveFunc(p Point, y float64) bool {
	return p.Y > y
}

/*
Compare the above function with the corresponding method -
func (p Point) IsAbove(y float64) bool {
	return p.Y > y
}
*/








func main() {
	p := Point{2.5, -3.0}

	fmt.Println("Point : ", p)

	fmt.Println("Is Point p located above the line y = 1.0 ? : ", IsAboveFunc(p, 1))
}

والسؤال الذي يطرح نفسه بقوة الآن هو, لماذا قد نستخدم الأساليب (ميثودز) بدلاً من الدوال العادية ؟

الميثودز تُساعدك على كتابة برنامجك بشكل أقرب مايكون للبرمجة كائنية التوجه في لغة غو.
وأكثر من ذلك فإن الميثودز تُساعدك على تجنب التضارب بأسماء الدوال بما أن الميثودز مرتبطة بمتلقي خاص, فيمكنك أن تستخدم نفس اسم الميثود عدة مرات بمتلقيات مختلفة, لنرى مثال آخر ..

package main

import (  
    "fmt"
    "math"
)

type Rectangle struct {  
    length int
    width  int
}

type Circle struct {  
    radius float64
}

func (r Rectangle) Area() int {  
    return r.length * r.width
}

func (c Circle) Area() float64 {  
    return math.Pi * c.radius * c.radius
}

func main() {  
    r := Rectangle{
        length: 10,
        width:  5,
    }
    fmt.Printf("Area of rectangle %d\n", r.Area())
    c := Circle{
        radius: 12,
    }
    fmt.Printf("Area of circle %f", c.Area())
}

والنتيجة ستكون

Area of rectangle 50  
Area of circle 452.389342

– متلقي قيمة أم متلقي مؤشر ؟!

في أمثلتنا السابقة رأينا أساليب لها متلقيات قيم value receivers.
يمكننا أيضاً أن ننشئ أسلوب بمتلقي مؤشر pointer receiver, الفرق بين “متلقي قيمة” و”متلقي مؤشر” هو أن التغييرات التي تحدث للبيانات داخل دالة الأسلوب ذات متلقي المؤشر تؤثر على أصل البيانات الممررة, ليتضح لنا الامر أكثر علينا بالمثال التالي ..

package main

import (
	"fmt"
)

type Employee struct {
	name string
	age  int
}

/*
Method with value receiver
*/




func (e Employee) changeName(newName string) {
	e.name = newName
}

/*
Method with pointer receiver
*/




func (e *Employee) changeAge(newAge int) {
	e.age = newAge
}

func main() {
	e := Employee{
		name: "Mark Andrew",
		age:  50,
	}
	fmt.Println("Employee name before change: ", e.name)
	e.changeName("Michael Andrew")
	fmt.Println("Employee name after change: ", e.name)

	fmt.Println("Employee age before change: ", e.age)
	(&e).changeAge(51)
	fmt.Println("Employee age after change: ", e.age)
}

نتيجة التنفيذ:

Employee name before change: Mark Andrew  
Employee name after change: Mark Andrew

Employee age before change: 50  
Employee age after change: 51

في المثال السابق, أسلوب changeName له متلقي قيمة, بينما أسلوب changeAge له متلقي مؤشر.
التغييرات لخاصية name للبنية Employee داخل أسلوب changeName لن تكون ظاهرة للمستدعي ولن تؤثر على أصل البيانات والمعلومات في ذاكرة البرنامج, ويمكن اعتبار أنها تقوم بتعديل مؤقت وظاهري فقط يزول عند الانتقال للسطر التالي,
بينما أسلوب changeAge له متلقي مؤشر بالتالي فهو يقوم بالتعديل على أصل بيانات المتغير e في ذاكرة البرنامج بشكل غير قابل للعكس.

في السطر السادس والثلاثون من المثال السابق كتبنا (&e).changeAge(51) لاستدعاء أسلوب changeAge, بما أن له متلقي مؤشر فاستخدمنا (&e) لاستدعاء الاسلوب ولكن هذا غير ضروري فيمكننا استخدام
e.changeAge(51) وسيكون نفس التأثير ويمكنك التجريب بنفسك.

متى نستخدم متلقي مؤشر ومتى نستخدم متلقي قيمة ؟
بشكل عام فإننا نستخدم متلقي مؤشر عندما نريد أن تكون التعديلات على البيانات ظاهرة ودائمة على طول البرنامج.
وأيضاً تُستخدم متلقيات المؤشرات عندما يكون لدينا حجم هائل من البيانات وذاكرة محدودة, فمتلقي القيمة يقوم بنسخ البيانات الأصلية الى موضع اخر جديد في الذاكرة ويعدل عليها وبالتالي يأخذ ذاكرة أكبر, بينما متلقي المؤشر لاينشئ أي نسخ جديدة, فقط يعدل على الأصل.
أما في أي حالات اخرى يمكنك استخدام متلقي قيمة.

والى هنا يكون قد انتهى درسنا, في حال وجدت أي صعوبة فيرجى مراجعة درس الدوال و درس المؤشرات.

ملاحظة: يمكنك تشغيل أكواد وأمثلة لغة غو من خلال GoLang Playground أو GoPlay.Space .

نبذة عن الكاتب

Firas M. Darwish

Software Engineer, PHP/Laravel, C#, GoLang, founder of ArGoLang.com & others ..

شاركنا رأيك :)