[Golang] Pointer receiver and Interface

Visarut Junsone
Lazy-Dev
Published in
3 min readJan 25, 2019

Hello, Golang world! วันนี้ เราจะพูดถึงสองเรื่องด้วยกัน ซึ่งคือ Pointer receiver และ interface จริงๆแล้ว อยากจะเขียนบทความเกี่ยวกับการ interface แต่มันดันมีเจ้าตัว Pointer receiver นี้สิ ที่ควรจะต้องทำความเข้าใจซะก่อน ไม่งั้นจะงงกันซะก่อน ว่าเจ้าสิ่งที่อยู่หน้าชื่อฟังก์ชั่น “มันคืออะไรกัน”, ”ไม่เคยเห็นมาก่อนเลย”

Pointer receiver

reference จาก Tour golang

Golang มีความสามารถในการประกาศฟังก์ชั่นให้มีสิ่งที่เรียกว่า Receiver ได้

“แล้ว pointer receiver คืออะไรละ?” ก็ตามชื่อครับ ก็คือ Receiver ที่เป็น pointer นั้นเอง

“ทำไมต้องใช้ Pointer receiver” มี 2 เหตุผลหลักๆ คือ 1. เพื่อให้ function ที่เราสร้างให้มี Pointer receiver สามารถเปลี่ยนค่าที่รับมาได้ 2. เพื่อการหลีกเลี่ยงการคัดลอกค่าในแต่ละ function ที่สร้างเพื่อใช้งาน สิ่งนี้จะมีประสิทธิภาพมากขึ้นถ้า struct ที่ใช้นั้นใหญ่มากๆ

ตัวอย่างเช่น Code snip ด้านล่าง

package apisimport "fmt"type Speed struct {     Distance, Time float64}func (s *Speed) SetParam() {     s.Distance = 100     s.Time = 5}func (s Speed) CalculateSpeed() float64 {     return s.Distance / s.Time}func TestReceiver() string {     v := &Speed{}     v.SetParam()     speed := v.CalculateSpeed()     return fmt.Sprintf("%s%f%s%f%s%f", "Distance = ", v.Distance, "m. Time = ", v.Time, "m/s. Speed = ", speed)}

เราจะมาทำการคำนวนอัตราเร็วกัน

  1. ประกาศ struct ของ speed ประกอบไปด้วย ระยะทาง (Distance) และ เวลา (Time)
  2. ประกาศ ฟังก์ชั่นแบบ Pointer receiver ชื่อ SetParam เพื่อตั้งค่าเริ่มต้น สังเกตว่าที่ type Speed ของ Receiver มีเครื่องหมายดอกจันอยู่ แสดงถึงความเป็น pointer ทำให้ค่าของ Speed ใดๆก็ตามที่ถูกส่งมาฟังกืชั่นนี้ถูกเปลี่ยนเป็น Distance = 100, Time = 5
  3. ประกาศ ฟังก์ชั่นแบบ Receiver ธรรมดา ชื่อ CalculateSpeed เพื่อคำนวนอัตราเร็ว ระยะทางต่อเวลา
  4. ประกาศ ฟังก์ชั่น TestReceiver เพื่อเป็นตัวทดสอบ เริ่มที่การประกาศค่า Speed แบบไม่ระบุค่าใด ๆ ทำให้ค่า Distance และ Time เป็น Zero value (ของ float64) ซึ่งคือ 0 และเรียก SetParam จะทำให้ค่าเปลี่ยน เป็น Distance = 100, Time = 5 และเมื่อเรียก CalculateSpeed ด้วย ค่าดังกล่าว จึงได้ค่า อัตราเร็ว 20
รูปที่ 1 แสดงผลลัพท์ของ Pointer receiver

Interface

Interface สามารถทำให้โค้ดของเรายืดหยุ่น, เชื่อมต่อกันอย่าง dependencies และ ปรับขนาดได้ ความสามารถของ Interface ก็คงจะคล้ายๆ กันกับ Interface ของภาษาอื่น ๆ แต่ Golang นั้น ออกแบบมาให้โค้ดมีความ สั้นและง่าย (สั้นไปคนอ่านก็ไม่ได้รู้สึกว่าง่ายนะ บ่นไปงั้น ฮ่าๆๆ)

ใน Interface ของ Go นั้น เป็นเรื่องที่ผมค่อนข้างสับสนทีเดียว และคือว่ามือใหม่หลายๆ คนแบบผม ก็คงจะเป็นเหมือนกัน เนื่องจากวิธีการ ในการ implement interface ของ Go นั้น ไม่เหมือนกับภาษาอื่นๆ เลย จะพยายามอธิบายแบบไม่ให้สับสนมากนัก แต่ตั้งชื่อให้ function ให้เป็นชื่อเดียวกันกัน interface ก็ทำการ implement อัตโนมัติแล้ว

มายกตัวอย่างกันเลยดีกว่า อธิบายเยอะไปเดี๋ยวงง

package apisimport "fmt"// Formula for moving in a straight linetype Formular interface {     GetSpeed() float64}type FirstSpeedFormular struct {     EarlySpeed, Acceleration, Time float64}// v = u + atfunc (s FirstSpeedFormular) GetSpeed() float64 {     return s.EarlySpeed + (s.Acceleration * s.Time)}type SecondSpeedFormular struct {     EarlySpeed, Acceleration, Distance float64}// v^2 = u^2 + 2asfunc (s SecondSpeedFormular) GetSpeed() float64 {     return (s.EarlySpeed * s.EarlySpeed) + (2 * s.Acceleration * s.Distance)}func HandleFormular() string {     first := FirstSpeedFormular{100, 5, 20}     second := SecondSpeedFormular{10, 5, 100}     var str string     allSpeed := make([]Formular, 2)     allSpeed[0] = first     allSpeed[1] = second     for _, speed := range allSpeed {          str = fmt.Sprintf("%s %f --- ", str, speed.GetSpeed())     }     return str}

จะเห็นว่าใน HandleFormular มีการใช้ FirstSpeedFormular และ SecondSpeedFormular และใน loop ในจะเห็นว่าเรียกใช้แค่ GetSpeed หลายคนคงจะงง ว่าเอ๊ะ! มันไปใช้ GetSpeed ของอันไหนมันไปอันที่ถูกเหรอ มันวิ่งไปหาของ struct SpeedFormular ของตัวมันเองครับเนื่องจากเราโยงมันหากันเองแล้ว

รูปที่ 2 แสดง Diagram

งงกันไหมครับเรื่อง Interface ถ้ามี feedback ว่าผมควรเพิ่มตรงไหน ถึงทำให้คนอื่นเข้าใจได้มากขึ้นจะดีมากเลยครับ จริงๆแล้วความสามารถของ Interface นั้น มีมากกว่านี้ เพราะ Interface ของ Golang นั้น เรียกได้ว่า จะเป็นอะไรก็ได้ ตัวอย่างเช่นในหัวข้อที่เคยพูดไป How to parse JSON Go-lang ในส่วน Unstructured data จะเห็นว่ามีการใช้ Interface ที่ map[string]interface{} เพื่อทำให้ค่าที่ใส่เข้ามานั้นจะเป็นอะไรก็ได้แล้วแต่จะใส่มาเลย

เรื่องราวใน blog ต่อไปจะเป็นอะไรนั้น เช่นเคยครับ ยังคิดไม่ออก ฮ่าๆๆๆ ตามดูละกันนะครับผม

--

--

Visarut Junsone
Lazy-Dev

I’m Full-Stack Developer. ReactJS Golang C# Javascript C++ PHP and learning a lot more. Not lazy as it name. “Lazy-Dev”