ออกตัวก่อนว่าไม่ได้เชี่ยวชาญ Go อะไรมากมาย แค่นึกไม่ออกว่าเขียนเรื่องอะไรดี แล้วเรื่องนี้ก็ผุดขึ้นมา
ใน Go สามารถทำ OOP ได้ แต่จะค่อนข้างต่างจากภาษาอื่นที่เคยเจอมา คือไม่มีสิ่งที่เรียกว่า class แต่จะใช้ struct ซึ่งเป็นการ define โครงสร้างข้อมูล เช่น
type Animal struct {
Name string
Weight int
}
เป็นการประกาศโครงสร้างของข้อมูลที่ประกอบด้วยฟิลด์ชื่อ Name และ Weight และเนื่องจากใน Go จะใส่ zero value ให้ค่าต่างๆ เสมอ ดังนั้นถ้าประกาศตัวแปรด้วย type ของ struct แต่ละ field ก็จะมีค่าเริ่มต้นเป็น zero value ของแต่ละชนิดข้อมูลโดยอัตโนมัติ ซึ่งก็คือทำหน้าที่เก็บข้อมูลได้เหมือน data class ในภาษาอื่นนั่นเอง
จากตัวอย่าง Cat ถ้าประกาศตัวแปรแบบนี้
cat := Animal{}
เราก็จะได้สิ่งที่เหมือนกับ instance ของ data class ที่มีค่า cat.Name = "" และ cat.Weight = 0
หรือจะกำหนดค่าเริ่มต้นลงไปตั้งแต่ตอนประกาศค่าก็ได้ เช่น
cat := Animal{Name: "Kitty", Weight: 2}
ก็จะได้ค่าตามที่กำหนดคือ cat.Name = "Kitty" และ cat.Weight = 2
Class with methods
ตอนนี้เราสามารถสร้างสิ่งที่เหมือนกับ class ที่มี property แล้ว แต่ว่าในโลกของ OOP เนี่ย class ไม่ได้มีแค่ property แต่ยังมี method ด้วยใช่มั้ย?
ใน Go เราสามารถสร้าง method ให้กับ struct ได้โดยไม่ได้เขียนลงใน struct เลย แต่ใช้การการผูกฟังก์ชั่นเข้าไปโต้งๆ ทีหลัง ซึ่งคำว่า "ผูกโต้งๆ" อาศัยสิ่งที่เรียกว่า receiver
ตัวอย่างเช่น เราจะเติม method Eat() ให้กับ Animal ได้แบบนี้
func(a *Animal) Eat() {
a.Weight++
}
สังเกตว่ารูปแบบการเขียนฟังก์ชั่นจะมีบางอย่างเพิ่มเข้ามา คือ (a *Animal) สิ่งนี้แหละที่เรียกว่า receiver โดย a คือ receiver variable ส่วน *Animal เป็น receiver type สังเกตว่าจะไม่มีการใช้คีย์เวิร์ด this เหมือนภาษาอื่น แต่จะใช้ receiver อ้างอิงไปเลย
ลองเขียนโค้ดทดสอบดู
cat := Animal{Name: "Kitty", Weight: 2}
cat.Eat()
fmt.Printf("Weight: %v", a.Weight)
จะได้ค่า a.Weight = 3
แล้วทีนี้อยากให้มี method อีกสักกี่อันก็แค่เขียนฟังก์ชั่นโดยผูก receiver เข้าไป
ข้อดีของการเขียนลักษณะนี้คือทำให้สามารถเติม method เข้าไปได้โดย struct ไม่บวม (อยู่คนละไฟล์ได้) และการใช้ชื่อตัวแปร receiver แทน this ทำให้ refactor โค้ดจาก function ธรรมดามาเป็น method ได้ง่ายๆ โดยไม่ต้องแก้ชื่อตัวแปรเป็น this
เสริมอีกนิดว่า receiver มี 2 แบบคือ value receiver กับ pointer receiver สำหรับในตัวอย่างเราใช้ pointer receiver ซึ่งคือแบบนี้ (a *Animal) เพราะต้องการแก้ไขข้อมูล เวลาใช้ value receiver ซึ่งคือแบบนี้ (a Animal) เราจะไม่สามารถแก้ไขข้อมูลได้
เช่น ถ้าแก้โค้ดของ method Eat() เป็นแบบนี้
// การใช้ value receiver จะไม่สามารถแก้ไขข้อมูลได้
func(a Animal) Eat {
a.Weight++
}
ผลที่ได้คือคือค่าของ a.Weight จะไม่เปลี่ยน
Inheritance (หรือจริงๆ คือ Composition)
การ "สืบทอด" ใน Go ก็แตกต่างจากภาษาอื่นที่ใช้คีย์เวิร์ด extends แต่ Go ออกแบบมาให้ "Composition over Inheritance" จึงใช้การ "ฝัง" (embedding) struct ซ้อนเข้าไปข้างใน เช่น
type Cat struct {
Animal
SlaveName string
}
func(c *Cat) Meow() {
fmt.Println("Meow")
}
เราจะได้ Cat ที่มี property และ method ของ Animal ติดมาด้วยทันที
ทดสอบด้วยการเขียนโค้ดตามด้านล่างนี้
cat := Cat{}
cat.Eat()
fmt.Printf("Name: %v, Weight: %v, SlaveName: %v", cat.Name, cat.Weight, cat.SlaveName)
โค้ดจะไม่พัง และจะได้ผลลัพธ์เป็น Name:, Weight:1, SlaveName:
-
Nameเป็นสตริงว่าง จาก zero value -
Weightเป็น 1 เนื่องจากมีการสั่ง cat.Eat() ทำการเพิ่มค่าจาก zero value ครั้งนึง -
SlaveNameเป็นสตริงว่างจาก zero value
แล้วถ้าเกิดเราต้องการใส่ค่าเริ่มต้นให้กับ cat ล่ะ? ในเมื่อเราสามารถเรียก property ที่มาจาก Animal ได้ตรงๆ งั้นเราลองใส่ Weight เป็น 2 ให้กับ cat ดูหน่อย
cat := Cat{Weight: 2, SlaveName: "Jack"}
ปรากฏว่าเขียนแบบนี้จะเจอ syntax error…
นี่เป็นเรื่องที่เราเจอตอนลองเขียนครั้งแรก แล้วก็งงว่าจะใส่ค่าเริ่มต้นให้มันได้ยังไง แล้วก็ค้นพบว่า วิธีที่ถูกต้องคือ ต้องระบุชื่อ struct ที่ embed มาด้วย!
เนื่องจากตอนประกาศ struct เราใส่ Animal เข้าไป ตอนเซ็ตค่าเริ่มต้นเราก็ต้องระบุ Animal เหมือนกัน แบบนี้
cat := Cat{Animal: Animal{Weight: 2}, SlaveName: "Jack"}
ทีนี้ถ้าลองสั่ง
cat.Eat()
fmt.Printf("Name: %v, Weight: %v, SlaveName: %v", cat.Name, cat.Weight, cat.SlaveName)
เราจะได้ผลลัพธ์ Name:, Weight:3, SlaveName:Jack
-
Nameยังเป็นสตริงว่างเพราะไม่ได้กำหนดค่า -
Weightเป็น 3 เนื่องจากค่าเริ่มต้นถูกเซ็ตเป็น 2 -
SlaveNameได้ค่า Jack ที่เซ็ตเข้าไป
ตอนที่ดึงค่าออกมาจาก field หรือเรียก method สามารถเรียกได้เลยตรงๆ แต่ตอนประกาศค่าต้องระบุชื่อ struct ที่ embed มาเสมอ
สำหรับ OOP ใน Go คร่าวๆ ก็ประมาณนี้ค่ะ
Top comments (0)