2.3 متد (Method)

2.3 متد (Method)

متد در واقع یک تابع گیرنده (receiver) است که به واسطه یک تایپ در دسترس خواهد بود. توجه کنید برای تعریف متد باید قبل از اسم تابع، داخل پرانتز یک نام و یک تایپ قرار دهید. برای درک بهتر این موضوع فکر کنید نامی که داخل پرانتز قرار می‌دید یه متغیر هست که به تایپ شما اشاره می‌کند. به مثال زیر توجه کنید:

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 نیست و یک الگوی پذیرفته شده حتی در زبان‌های Functional programming languages نیز است.

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}
1$ go run main.go
2Name: Sam
3Age: 31
4Salary 2000

در کد بالا ما یک ساختار با نام 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}
1$ go run main.go
2Name: Sam
  • علت اینکه می‌گوییم خیر : به خاطر اینکه ما داریم با یک کپی از فیلدهای ساختار کار می‌کنیم و با تغییر مقدار هر یک از فیلدها تغییر بر روی کپی آن اعمال خواهد شد.
  • اما علت اینکه می‌گوییم بله : اگر ما با استفاده از اشاره‌گر pointer به فیلدهای داخل ساختار دسترسی پیدا کنیم می‌توانیم مستقیماً به داخل خانه حافظه تایپ دسترسی داشته باشیم و مقدار فیلد مورد نظر را در هر جایی از پروژه تغییر دهیم.

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}
1$ go run main.go
2Name: John

در مثال بالا متد 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}
1$ go run main.go
2Name: John
3Name: Mike

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}
1$ go run main.go
2City: London
3Country: UK

در مثال بالا ما یک متد برای ساختار address تعریف کردیم و سپس ساختار address را داخل ساختار employee گذاشتیم. در نهایت شما با استفاده از employee می‌توانید به متدهای address هم دسترسی داشته باشید و از آن‌ها استفاده کنید.