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

Interfaces

Interface คือชื่อที่รวบรวม method signatures ไว้ด้วยกัน type ใดที่มี methods ทั้งหมดนั้นจะตอบสนอง interface — โดยไม่ต้องบอกอย่างชัดเจน

type Shape interface {
Area() float64
Perimeter() float64
}

type ใดที่มี method Area() float64 และ Perimeter() float64 จะตอบสนอง Shape โดยอัตโนมัติ

นี่คือคุณสมบัติที่กำหนดตัวตน interfaces ของ Go เปรียบเทียบ:

ภาษาวิธีประกาศการตอบสนอง
Java / C#class Circle implements Shape
Goแค่มี methods ที่ถูกต้อง

ผลลัพธ์คือโค้ดที่ decoupled type ใน package geometry สามารถตอบสนอง interface ใน package renderer โดยที่ไม่มี package ใดรู้จักอีก package ทำให้ Go interfaces compose ข้าม package boundaries ได้โดยไม่มี import dependency บน interface definition

standard library ของ Go สร้างบน interfaces เล็ก ๆ ที่มีจุดโฟกัส:

type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type Stringer interface { String() string }

คำสอนของ Go: “ยิ่ง interface ใหญ่ abstraction ยิ่งอ่อนแอ” interface สอง methods ถูกตอบสนองโดย types จำนวนมากกว่า interface สิบ methods อย่างมาก ทำให้ reuse ได้มากกว่าอย่างมหาศาล

Interface ที่ไม่มี method ถูกตอบสนองโดยทุก type ตั้งแต่ Go 1.18 มี alias ว่า any:

var v any = 42
v = "hello"
v = []int{1, 2, 3}

ใช้ any อย่างประหยัด — เพราะสละ type safety กรณีใช้งานปกติ: generic containers (ก่อนที่ Go จะมี generics), JSON unmarshalling รูปร่างที่ไม่รู้จัก, และฟังก์ชัน logging แบบ variadic

แนวทาง Go ที่ idiomatic:

  • Function parameters ควรเป็น interfaces — รับ type กว้างที่สุดที่ครอบคลุมสิ่งที่คุณเรียกจริง
  • Return values ควรเป็น concrete structs — ผู้เรียกได้รับ API เต็มของ type จริง; พวกเขา narrow เองได้
// ดี: รับ interface, คืน concrete type
func NewBuffer(r io.Reader) *bytes.Buffer { ... }
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
type Rect struct {
W, H float64
}
func (r Rect) Area() float64 { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }
func describe(s Shape) {
fmt.Printf("area=%.2f perimeter=%.2f\n", s.Area(), s.Perimeter())
}
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rect{W: 4, H: 3},
}
for _, s := range shapes {
describe(s)
}
}
interface value คือ 2 words:
iface
├─ *itab → ┌──────────────────────┐
│ │ type: *Circle │
│ │ methods: [Area, │
│ │ Perimeter] │
│ └──────────────────────┘
└─ data → Circle{Radius: 5}
dynamic dispatch:
shape.Area()
itab.methods[0](*data) ← indirect call
Circle.Area(data)
สิ่งที่ได้ประโยชน์ต้นทุน
implicit satisfactiondecoupled, no import dependencyยากต่อการ discover ว่า type ใด implement interface ใด
small interface (1-2 methods)composable, implement ง่ายอาจต้องใช้หลาย interface ร่วมกัน
any / empty interfaceรับ type ใดก็ได้สูญเสีย type safety ทั้งหมด
dynamic dispatchflexibility~5-10ns overhead ต่อ call vs direct call
  • interface ฟรี — interface เพิ่ม indirection 1 ชั้นและ dynamic dispatch overhead
  • nil interface และ interface ที่เก็บ nil pointer เป็นเหมือนกัน — nil interface คือ (nil, nil), interface ที่เก็บ nil pointer คือ (*T, nil) ซึ่งไม่เท่ากัน
  • ยิ่ง interface มี method มาก ยิ่งดี — Go idiom: “the bigger the interface, the weaker the abstraction”
  • implicit satisfaction แปลว่า duck typing — Go ตรวจสอบ interface ตอน compile ไม่ใช่ runtime

💡 ตัวอย่างจากของจริง

io.Reader (1 method): http.Body, os.File, bytes.Buffer, strings.Reader ล้วน implement — composable ทั่ว ecosystem โดยไม่มี import dependency

Kubernetes ใช้ runtime.Object interface ให้ทุก Kubernetes resource ทำงานกับ generic controller ได้ — decoupling ผ่าน small interface

type ใน Go ประกาศว่า implement interface อย่างไร?
คุณนิยาม interface `Printer` ที่มีหนึ่ง method ใน package `ui` struct `Document` ใน package `doc` มี method นั้น ต้องการ import อะไรเพิ่ม?
`any` หมายถึงอะไรใน Go 1.18+?
ฟังก์ชันของคุณต้องการเขียน bytes ควรใช้ parameter type ใดที่ idiomatic ที่สุด?