ข้ามไปยังเนื้อหา

Methods & Interfaces

ภาษาเชิงวัตถุส่วนใหญ่ผูก 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 Assertionsx.(T), comma-ok form, และ switch v := x.(type)
EmbeddingStruct 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 satisfactiondecoupled code, ไม่มี import dependency บน interfaceยากต่อการ discover ว่า type ใด implement interface ใด
value receiverimmutable, เรียกได้บน non-addressable valuescopy struct ทุกครั้ง
pointer receivermutate ได้, ไม่ 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

ในทุกบทของโมดูลนี้ มีเช็กลิสต์กลุ่มเดียวกันที่จับ 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 nil pointer ผ่าน 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}

type ใน Go ประกาศว่าตอบสนอง interface อย่างไร?
โค้ดนี้ compile ผ่านไหม?
type Rectangle struct{ W, H float64 }
func (r *Rectangle) Area() float64 { return r.W * r.H }
type Sizer interface{ Area() float64 }

var s Sizer = Rectangle{W: 4, H: 3}
โค้ดนี้ print อะไร และทำไม?
type MyErr struct{ msg string }
func (e *MyErr) Error() string { return e.msg }

func f() error {
    var p *MyErr
    return p
}

fmt.Println(f() == nil)
อะไรที่สามารถมี methods ใน Go ได้?
อะไรคือทางเลือกของ Go แทน class inheritance?