Methods & Interfaces
แนวคิด polymorphism ของ Go
หัวข้อที่มีชื่อว่า “แนวคิด polymorphism ของ Go”ภาษาเชิงวัตถุส่วนใหญ่ผูก polymorphism ไว้กับลำดับชั้นของ class: คุณต้องประกาศว่า type หนึ่ง implements interface ใด, ต้อง extends base class, และ compiler จะบังคับโครงสร้างสายเลือดนั้น Go ตัดทิ้งลำดับชั้นทั้งหมด
ใน Go named type ใดก็ตามสามารถมี method ได้ (ยกเว้น pointer types และ interface types) และ type ใดที่มี method ถูกต้องก็ตอบสนอง interface โดยอัตโนมัติ — ไม่ต้องประกาศ นี่คือ implicit interface satisfaction และเป็นการตัดสินใจออกแบบที่สำคัญที่สุดของภาษา
การผสม methods และ interfaces ให้คุณได้:
- Polymorphism โดยไม่มีต้นไม้ inheritance
- Dependency inversion โดยไม่ต้องพึ่ง framework — รับ interface, คืน struct
- Composition โดยไม่ต้องใช้
extends— embed types เพื่อ promote fields และ methods
ทุกกฎในโมดูลนี้จบลงที่อย่างใดอย่างหนึ่งตอนคุณเขียนโค้ดจริง: compile error ที่ระบุชื่อ method ที่ขาดหรือไม่ตรง หรือ nil surprise ข้างใน interface หรือ embedded pointer การฝึกจำทั้งสองแบบให้ได้ทันทีที่เห็น — ไม่ใช่แค่รู้กฎในเชิงนามธรรม — คือทักษะจริงที่โมดูลนี้สร้าง
แผนที่โมดูล
หัวข้อที่มีชื่อว่า “แผนที่โมดูล”| บทเรียน | สิ่งที่คุณจะเรียนรู้ |
|---|---|
| Methods | ประกาศ methods บน named type ใดก็ได้; value vs pointer receiver; method sets |
| Interfaces | นิยามและตอบสนอง interface โดยนัย; small interfaces; empty interface any |
| Type Assertions | x.(T), comma-ok form, และ switch v := x.(type) |
| Embedding | Struct embedding, promoted fields/methods, interface embedding, composition แทน inheritance |
ตัวอย่างแรกที่รันได้
หัวข้อที่มีชื่อว่า “ตัวอย่างแรกที่รันได้”โค้ดด้านล่างนิยาม method บน struct, ประกาศ interface หนึ่ง method, และเรียก method ผ่าน interface — ทั้งหมดในไม่ถึง 20 บรรทัด
package main
import "fmt"
type Rectangle struct { Width, Height float64}
func (r Rectangle) Area() float64 { return r.Width * r.Height}
type Sizer interface { Area() float64}
func printArea(s Sizer) { fmt.Printf("area = %.2f\n", s.Area())}
// Counter shows the other half of the story: a pointer receiver that mutates.type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
func main() { r := Rectangle{Width: 4, Height: 3} fmt.Println(r.Area()) printArea(r)
c := &Counter{} c.Inc() c.Inc() fmt.Println(c.n) // 2 — pointer receiver mutated the original} ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
| implicit interface satisfaction | decoupled code, ไม่มี import dependency บน interface | ยากต่อการ discover ว่า type ใด implement interface ใด |
| value receiver | immutable, เรียกได้บน non-addressable values | copy struct ทุกครั้ง |
| pointer receiver | mutate ได้, ไม่ copy | ต้อง nil-check, ต้องการ addressable value |
| embedding (composition) | reuse methods/fields โดยไม่ inherit | ไม่มี virtual dispatch — ต้องใช้ interface สำหรับ polymorphism |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- interface คือ abstract class — ใน Go interface มีแค่ method signatures ไม่มี implementation, ไม่มี state
- embedding = inheritance — embedded type ไม่รู้จัก outer type ไม่มี override หรือ virtual dispatch
- nil interface เสมอ == nil — interface ที่เก็บ nil pointer ไม่ใช่ nil interface; (nil, nil) != (*T, nil)
💡 ตัวอย่างจากของจริง
Go standard library ใช้ io.Reader และ io.Writer เป็น interface หลัก — os.File, bytes.Buffer, net.Conn, http.Body ล้วน implement สิ่งเดียวกัน ทำให้ compose ได้ทั่ว ecosystem
Kubernetes ใช้ interface
runtime.Object(k8s.io/apimachinery/pkg/runtime) กับทุก Kubernetes resource — controller เขียนครั้งเดียวทำงานกับ Pod, Deployment, Service ได้ทุก type
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”ในทุกบทของโมดูลนี้ มีเช็กลิสต์กลุ่มเดียวกันที่จับ bug ของโค้ดที่ generate มาได้ส่วนใหญ่ ก่อนที่มันจะถูกส่งใช้จริง:
- ทุก method บน type ใช้ receiver kind เดียวกันหรือเปล่า? การผสมกัน หรือ value receiver บน type ที่ตั้งใจจะ mutate คือสิ่งแรกที่ควรเช็ก
- concrete value ถูก assign ให้ interface variable ทั้งที่ method ที่ต้องการมี pointer receiver หรือเปล่า? นั่นคือ compile error ไม่ใช่ runtime bug — อ่านข้อความ มันระบุชื่อ method ให้เลย
- function return typed
nilpointer ผ่าน interface-typed result (errorหรือ interface อื่น) หรือเปล่า? การเช็ก== nilของ caller จะผิด - single-value type assertion (
v := x.(T)) รันโดยไม่มี comma-ok guard บน value ที่อาจไม่เก็บTหรือเปล่า? - embedded pointer หรือ embedded interface ที่ใช้เป็น partial mock ถูกเรียก promoted method ก่อนที่จะมีอะไรมา assign ค่าจริงให้หรือเปล่า?
🐛 หา bug ในโค้ดนี้
type Rectangle struct { Width, Height float64}
func (r *Rectangle) Area() float64 { return r.Width * r.Height}
type Sizer interface { Area() float64}
func main() { var s Sizer = Rectangle{Width: 4, Height: 3} _ = s}เฉลย
Area มี receiver แบบ pointer ดังนั้น method set ของ Rectangle (value type) จึงไม่มี method นี้ — มีแค่ method set ของ *Rectangle เท่านั้น การ assign Rectangle{} value ธรรมดาให้ interface variable Sizer จึง compile ไม่ผ่าน: cannot use Rectangle{…} (value of struct type Rectangle) as Sizer value in variable declaration: Rectangle does not implement Sizer (method Area has pointer receiver) ทางแก้คือ var s Sizer = &Rectangle{Width: 4, Height: 3}