DEV Community

Cover image for Interface in Go ฉบับรวบยอด
Perajit
Perajit

Posted on

Interface in Go ฉบับรวบยอด

Interface กับ OOP

จากบทความที่แล้ว เราทำ OOP ด้วยการผูก method ให้ struct ผ่าน receiver ทีนี้ถ้าเราอยากจัดการกับ struct หลายประเภทที่มีพฤติกรรมเหมือนกันในที่เดียวจะทำยังไง?

ปกติในภาษาอื่นเราก็จะสร้าง interface ขึ้นมา แล้วให้แต่ละ class implement interface นั้นใช่มั้ย? ใน Go ก็มี interface เหมือนกัน แต่สิ่งที่ต่างคือ ใน Go เป็น implicit interface ไม่ต้องมีการเขียนคีย์เวิร์ด implement เลย

เช่น เรามี Cat และ Dog ซึ่งทั้งคู่มี method ที่มี method signature เหมือนกันคือ Speaker()

type Cat struct { Name string }
func (c Cat) Speak() { fmt.Println(c.Name, "Meow!") }

type Dog struct { Name string }
func (d Dog) Speak() { fmt.Println(d.Name, "Woof!") }
Enter fullscreen mode Exit fullscreen mode

เราสามารถสร้าง interface ได้แบบนี้

type Speaker interface {
  Speak()
}
Enter fullscreen mode Exit fullscreen mode

โดย Cat และ Dog ของเราจะยังเหมือนเดิม ไม่มีแก้โค้ดใดๆ แต่ Go จะรู้เองว่ามันคือ Speaker เพราะว่า "satisfy interface"

และเราก็สามารถประกาศ slice ของ Speaker แล้วเก็บค่า Cat กับ Dog ไว้ด้วยกัน รวมถึงสามารถเรียก method ของ Speaker จาก slice ได้เลย

cat := Cat{Name: "Kitty"}
dog := Dog{Name: "Bo"}

// slice ของ Speaker เก็บได้ทั้ง cat และ dog ทันที
animals := []Speaker{cat, dog}

for _, a := range animals {
  // เรียก Speak() ของ Speaker ได้เลย
  a.Speak()
}
Enter fullscreen mode Exit fullscreen mode

การที่ทำ interface ในรูปแบบนี้เพราะ Go เชื่อในความอิสระ โดยใช้แนวคิด Duck Typing (ถ้ามันเดินเหมือนเป็ด ร้องเหมือนเป็ด มันก็ย่อมเป็นเป็ด)

ใน classic OOP เวลาจะใช้ interface ต้องมีการผูกกันตั้งแต่ต้น แต่สำหรับ Go ใช้การประกาศ interface จากฝั่ง consumer ซึ่งมีข้อดีคือ

  • ทำให้เราโฟกัสที่ behavior มากกว่า type
  • ยืดหยุ่น สามารถเอา library สองตัวจากคนละที่มาทำงานร่วมกันได้ทันทีถ้า method signature ตรงกัน
  • ไม่ผูกติดกับโค้ดต้นทาง ถ้าสนใจ method ไหนก็ประกาศ interface แค่นั้นพอ ซึ่งข้อนี้ทำให้การ mock (เช่นสำหรับ unit test) ง่ายขึ้นมาก

โครงสร้าง Interface

ภายใต้ตัวแปร interface จะเก็บค่า 2 อย่างเสมอ คือ type (ชนิดของข้อมูล) และ value (ค่าของข้อมูล) เพราะฉะนั้น interface จะเป็น nil ก็ต่อเมื่อทั้ง type และ value เป็น nil

เช่นจากตัวอย่างเดิม เรามี struct Cat และ interface Speaker

var c *Cat = nil
var i Speaker = c

// ตรวจสอบว่า i เป็น nil รึเปล่า
fmt.Println(i == nil)
Enter fullscreen mode Exit fullscreen mode

จะได้ผลลัพธ์เป็น false

Empty Interface

interface ว่าง interface{} คือ interface ที่ไม่มีการกำหนด method อะไรไว้ ซึ่งหมายความว่าสามารถเป็น type อะไรก็ได้ (หรือก็คือ any ในเวอร์ชั่นใหม่ๆ ของ Go)

เรามักจะใช้ในกรณีที่ต้องการ Dynamic Type เช่น ใช้กับฟังก์ชั่นที่รับพารามิเตอร์อะไรก็ได้ หรือใช้กับโครงสร้างข้อมูลที่ dynamic เช่น map[string]interface{}

แต่ข้อควรระวังคือ เราจะไม่สามารถใช้ความสามารถของ type เดิมได้ ต้องแปลงค่ากลับมาก่อน เช่น

var x interface{} = 1
Enter fullscreen mode Exit fullscreen mode

เราไม่สามารถสั่งแบบนี้ได้

sum := x + 5 // จะ error
Enter fullscreen mode Exit fullscreen mode

เนื่องจากตอนนี้ x เป็น interface{} ไม่ใช่ int ต้องแปลงกลับเป็น int ก่อนถึงจะทำ operation ของ int ได้

sum := x.(int) + 5
Enter fullscreen mode Exit fullscreen mode

ปัจจุบัน Go มี Generics ให้ใช้แล้ว แนะนำให้ใช้ Generics แทน interface{}

Top comments (0)