متد در واقع یک تابع گیرنده (reciver) است که به واسطه یک تایپ در دسترس خواهد. توجه کنید برای تعریف تابع متد باید قبل از اسم تابع پرانتز قرار دهید و داخلش یک نام و تایپ مورد (reciver type)
نظر را قرار دهید. در زیر یک نمونه از متد را قرار دادیم:
1func (receiver receiver_type) some_func_name(arguments) return_values
برای درک بهتر این مفهوم می توانید متد را دقیقا یک تابع در نظر بگیرید و دو نوع تعریف زیر را عملا یکسان در نظر بگیرید. نحوه تعریف به صورت متد صرفا برای راحتی در زمان توسعه نرم افزار می باشد که به برنامه نویس امکان توسعه ای روان تر بدون نیاز به حفظ کردن زیاد عملکرد های سیستم را می دهد و عملا کامپایلر نیازی به تمایز دادن این دو تعریف ندارد.
1func (r receiver_T) some_func_name(arg1 arg1_T, ...) return_values
2func some_func_name(r receiver_T, arg1 arg1_T, ...) return_values
نکته قابل ذکر دیگر در خصوص این مفهوم این می باشد که متد در زبان گو از رویکرد static method به صورت مستقیم پشتیبانی نمی کند، یعنی تا زمانیکه شما یک متغیر از نوع تایپی که دارای متد می باشد را راه اندازی نکنید به متدهایش دسترسی نخواهید داشت.
اکثرا متد را یکی از عناوین شی گرایی در زبان گو میشناسند که مزایای خوبی دارد بخصوص اگر متدها برای تایپ struct تعریف شوند شما می توانید برای هریک از فیلدهای ساختارتان توابع بخصوصی در قالب متد بنویسید. ولی اگر بخوایم کمی دقیق تر بگیم مفهوم متد برگفته از الگوی Encapsulation که بر خلاف تصور رایج صرفا محدود به رویکرد OOP نمی شود و یک الگوی پذیرفته شده حتی در زبان های FP نیز می باشد.
2.3.1 متدها برای ساختار (struct) #
زبان گو یک زبان شی گرا نیست ولی برخی از مفاهیم شی گرایی را بصورت قرار دادی دارد که شما می توانید در کدهای خود استفاده کنید. ساختار در زبان گو یک تایپ می باشد که این تایپ کالکشنی از تایپ های مختلف می باشد که بخش قبلی ما بهش پرداختیم.
در زیر یک مثال در خصوص استفاده از متدها برای ساختار زدیم :
1package main
2
3import "fmt"
4
5type employee struct {
6 name string
7 age int
8 salary int
9}
10
11func (e employee) details() {
12 fmt.Printf("Name: %s\n", e.name)
13 fmt.Printf("Age: %d\n", e.age)
14}
15
16func (e employee) getSalary() int {
17 return e.salary
18}
19
20func main() {
21 emp := employee{name: "Sam", age: 31, salary: 2000}
22 emp.details()
23 fmt.Printf("Salary %d\n", emp.getSalary())
24}
در کد بالا ما یک ساختار با نام employee ایجاد کردیم و سپس برایش ۲ تا متد با نام های details و getSalary تعریف کردیم. حال برای اینکه بتوانیم از این متدها استفاده کنیم داخل تابع main ما یک متغیر از نوع employee تعریف کردیم و سپس با استفاده از نقطه .
پس از نام متغیر به متدها دسترسی پیدا کردیم همانند دسترسی به فیلدهای ساختار.
آیا با استفاده از متد می توانیم مقدار یکی از فیلدهای داخل ساختار را تغییر دهیم ؟ این سوال ۲ جواب دارد هم آره و هم خیر
حال به مثال زیر توجه کنید تا توضیح دهیم :
1package main
2
3import "fmt"
4
5type employee struct {
6 name string
7 age int
8 salary int
9}
10
11func (e employee) setNewName(newName string) {
12 e.name = newName
13}
14
15func main() {
16 emp := employee{name: "Sam", age: 31, salary: 2000}
17 emp.setNewName("John")
18 fmt.Printf("Name: %s\n", emp.name)
19}
- علت اینکه می گوییم خیر : به خاطر اینکه ما داریم با یک کپی از فیلدهای ساختار کار می کنیم و با تغییر مقدار هر یک از فیلدها تغییر صورت نمی پذیرد.
- اما علت اینکه می گوییم آره : اگر ما با استفاده از اشاره گر به فیلدهای داخل ساختار دسترسی پیدا کنیم می توانید مستقیما داخل خانه حافظه مشخص شده مقدار فیلد مورد نظر ساختار را در هرجایی از پروژه تغییر دهیم.
2.3.2 استفاده از اشاره گر (pointer) در متدها #
در بالا ما مثالی زدیم و اشاره کردیم به اینکه آیا می توانید مقدار هر یک از فیلدهای ساختار را با استفاده از متد تغییر دهیم یا خیر و در پاسخ گفتیم آره و خیر و علت آره را توضیح دادیم. حال می خواهیم با یک مثال این مورد را توضیح دهیم.
1package main
2
3import "fmt"
4
5type employee struct {
6 name string
7 age int
8 salary int
9}
10
11func (e *employee) setNewName(newName string) {
12 e.name = newName
13}
14
15func main() {
16 emp := &employee{name: "Sam", age: 31, salary: 2000}
17 emp.setNewName("John")
18 fmt.Printf("Name: %s\n", emp.name)
19}
در بالا متد setNewName یک نوع متد گیرنده از نوع اشاره گر است که ما داخل این تابع متد به مقدار فیلدهای داخل خانه حافظه ساختار employee دسترسی داریم و می توانیم مقدار دهی کنیم.
آیا استفاده از گیرنده اشاره گر واقعا ضروری است؟ خیر, ضروری نیست زیرا ما وقتی به متدها دسترسی داریم که یک نمونه (instance) از تایپ مورد نظر ایجاد کنیم تا به متدهایش دسترسی داشته باشیم و همچنین اگر فرضا نیاز داشته باشیم که یکی از فیلد های ساختار را مقدار دهی کنیم بازم می توانیم به آدرس خانه متغیری که ساختار راه نگه داری می کند اشاره کنیم و مقدارش را تغییر دهیم.
1package main
2
3import "fmt"
4
5type employee struct {
6 name string
7 age int
8 salary int
9}
10
11func (e *employee) setNewName(newName string) {
12 e.name = newName
13}
14
15func main() {
16 emp := employee{name: "Sam", age: 31, salary: 2000}
17 emp.setNewName("John")
18
19 fmt.Printf("Name: %s\n", emp.name)
20
21 (&emp).setNewName("Mike")
22 fmt.Printf("Name: %s\n", emp.name)
23}
2.3.2.1 چه موقع باید از گیرنده اشاره گر برای متد استفاده کنیم #
- زمانیکه قصد داریم متدهایی بنویسیم که برروی مقدار فیلدهای ساختار در زمان اجرا تغییراتی انجام دهد.
- زمانیکه ساختار خیلی بزرگ است و کلی فیلد دارد در اینجا بهتر از گیرنده اشاره گر استفاده کنیم تا هر بار با یکی کپی از ساختار مواجه نشویم و اینکار سربار را کم می کند.
2.3.4 تعریف متد برای فیلدهای ساختار تو در تو (nested) #
شما می توانید برای فیلدهایی که ساختار تو در تو دارد متد بنویسید :
1package main
2
3import "fmt"
4
5type employee struct {
6 name string
7 age int
8 salary int
9 address address
10}
11
12type address struct {
13 city string
14 country string
15}
16
17func (a address) details() {
18 fmt.Printf("City: %s\n", a.city)
19 fmt.Printf("Country: %s\n", a.country)
20}
21
22func main() {
23 address := address{city: "London", country: "UK"}
24
25 emp := employee{name: "Sam", age: 31, salary: 2000, address: address}
26
27 emp.address.details()
28}
در بالا ما یک متد برای ساختار address تعریف کردیم و سپس ساختار address را داخل ساختار employee گذاشتیم. در نهایت شما با استفاده از employee می توانید به متدهای address هم دسترسی داشته باشید و استفاده کنید.