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!") }
เราสามารถสร้าง interface ได้แบบนี้
type Speaker interface {
Speak()
}
โดย 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()
}
การที่ทำ 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)
จะได้ผลลัพธ์เป็น false
Empty Interface
interface ว่าง interface{} คือ interface ที่ไม่มีการกำหนด method อะไรไว้ ซึ่งหมายความว่าสามารถเป็น type อะไรก็ได้ (หรือก็คือ any ในเวอร์ชั่นใหม่ๆ ของ Go)
เรามักจะใช้ในกรณีที่ต้องการ Dynamic Type เช่น ใช้กับฟังก์ชั่นที่รับพารามิเตอร์อะไรก็ได้ หรือใช้กับโครงสร้างข้อมูลที่ dynamic เช่น map[string]interface{}
แต่ข้อควรระวังคือ เราจะไม่สามารถใช้ความสามารถของ type เดิมได้ ต้องแปลงค่ากลับมาก่อน เช่น
var x interface{} = 1
เราไม่สามารถสั่งแบบนี้ได้
sum := x + 5 // จะ error
เนื่องจากตอนนี้ x เป็น interface{} ไม่ใช่ int ต้องแปลงกลับเป็น int ก่อนถึงจะทำ operation ของ int ได้
sum := x.(int) + 5
ปัจจุบัน Go มี Generics ให้ใช้แล้ว แนะนำให้ใช้ Generics แทน interface{}
Top comments (0)