Methods
Method คืออะไร?
หัวข้อที่มีชื่อว่า “Method คืออะไร?”Method คือฟังก์ชันที่มี receiver — parameter พิเศษที่ปรากฏระหว่าง keyword func กับชื่อฟังก์ชัน receiver ผูกฟังก์ชันนั้นเข้ากับ named type
func (r Rectangle) Area() float64 { return r.Width * r.Height}ที่นี่ r Rectangle คือ receiver Area กลายเป็น method ของ Rectangle เรียกได้ว่า rect.Area()
Value receiver vs Pointer receiver
หัวข้อที่มีชื่อว่า “Value receiver vs Pointer receiver”Receiver สามารถเป็น value หรือ pointer ก็ได้:
func (c Counter) Value() int { return c.count } // value receiverfunc (c *Counter) Inc() { c.count++ } // pointer receiverValue receiver (t T) | Pointer receiver (t *T) | |
|---|---|---|
| เห็น | สำเนาของค่า | ค่าต้นฉบับ |
| แก้ไขได้? | ไม่ได้ — แก้เฉพาะสำเนา | ได้ |
| เรียกได้จาก | ทั้ง T และ *T | ทั้ง T (addressable) และ *T |
| ใช้เมื่อ | อ่านอย่างเดียว; ขนาดเล็ก, copy ถูก | ต้องแก้ไข; struct ใหญ่; method set สม่ำเสมอ |
ความสม่ำเสมอ: ถ้า method ใด ๆ บน type ต้องการ pointer receiver ให้ใช้ pointer receiver กับ ทุก method ของ type นั้น การผสมกันทำให้เกิดปัญหา method set ที่ละเอียดอ่อนเมื่อ type ถูกใช้เป็น interface value
Methods บน named type ใดก็ได้
หัวข้อที่มีชื่อว่า “Methods บน named type ใดก็ได้”Methods ไม่จำกัดแค่ structs คุณนิยาม methods บน named type ใด ๆ ใน package ของคุณได้:
type Celsius float64
func (t Celsius) String() string { return fmt.Sprintf("%.1f°C", float64(t))}คุณไม่สามารถนิยาม method บน type จาก package อื่น (เช่น func (s string) Upper() ผิดกฎ) แต่การห่อ type นั้นใน named type ในท้องถิ่นใช้ได้เสมอ
Method sets
หัวข้อที่มีชื่อว่า “Method sets”Method set ของ type กำหนดว่า interfaces ใดที่ตอบสนองได้:
- Method set ของ
Tมี methods ทั้งหมดที่มี value receiver(t T) - Method set ของ
*Tมี methods ทั้งหมด — ทั้ง(t T)และ(t *T)
นี่คือเหตุผลที่ interface ที่ต้องการ pointer-receiver method สามารถตอบสนองได้เฉพาะโดย *T ไม่ใช่ T
package main
import "fmt"
type Counter struct { count int}
// Pointer receiver — mutates the structfunc (c *Counter) Inc() { c.count++}
// Value receiver — read-onlyfunc (c Counter) Value() int { return c.count}
type Celsius float64
func (t Celsius) String() string { return fmt.Sprintf("%.1f\u00b0C", float64(t))}
func main() { c := Counter{} c.Inc() c.Inc() c.Inc() fmt.Println(c.Value())
temp := Celsius(36.6) fmt.Println(temp.String())}Loading Go runtime (first run only, ~8 MB)…
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
| value receiver | immutable, เรียกได้บน non-addressable values | copy struct ทุกครั้งที่เรียก |
| pointer receiver | mutate ได้, ไม่ copy struct | ต้อง nil-check, pointer ต้อง addressable |
| method บน non-struct type | ขยาย behavior ของ any type ใน package | ไม่สามารถ add method บน imported type ได้ |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- value receiver และ pointer receiver ใช้แทนกันได้ — value receiver ทำงานบนสำเนา ไม่สามารถ mutate receiver ได้
- สามารถเพิ่ม method บน type ของ package อื่นได้ — Go ไม่อนุญาต ต้อง wrap หรือ embed แทน
- *method set ของ T และ T เหมือนกัน — *T มี method set ใหญ่กว่า (รวมทั้ง pointer-receiver methods) ส่งผลต่อ interface satisfaction
💡 ตัวอย่างจากของจริง
net/http: http.ResponseWriter เป็น interface — Handler ทุกตัวรับ interface นี้แทน concrete type ทำให้ test ได้ง่ายด้วย mock
Kubernetes: reconcile loop ใช้ pointer receiver ทุกตัวเพราะ controller มี state ที่ต้องแก้ไข — consistency ของ receiver type สำคัญมาก