1 การทำความเข้าใจเกี่ยวกับอินเตอร์เฟซ

1.1 อินเตอร์เฟซคืออะไร

ใน Go language, อินเตอร์เฟซคือชนิดของข้อมูลที่เป็นชนิดข้อมูลทางนามธรรม อินเตอร์เฟซซ่อนรายละเอียดของการปฏิบัติในระดับพิเศษและแสดงเฉพาะพฤติกรรมของวัตถุต่อผู้ใช้เท่านั้น อินเตอร์เฟซกำหนดชุดของเมทอด แต่เมทอดเหล่านั้นไม่ได้ปรับปรุงความสามารถใด ๆ แทนที่มันถูก提供โดยชนิดที่เฉพาะเจาะจง คุณสมบัติของอินเตอร์เฟซใน Go language คือความไม่แทรกแทรง ซึ่งหมายความว่าชนิดหนึ่งไม่จำเป็นต้องประกาศโดยชัดเจนว่ามันทำอินเตอร์เฟซใด มันคือแค่จำเป็นต้องให้เมทอดที่จำเป็นโดยอินเตอร์เฟซนั้นๆ

// กำหนดอินเตอร์เฟซ
type Reader interface {
    Read(p []byte) (n int, err error)
}

ในอินเตอร์เฟซ Reader ใดๆ ที่มี Read(p []byte) (n int, err error) เมทอดสามารถถือว่าทำการ implement อินเตอร์เฟซ Reader ได้

2 การกำหนดอินเตอร์เฟซ

2.1 โครงสร้างการเขียนอินเตอร์เฟซ

ใน Go language, การกำหนดอินเตอร์เฟซมีโครงสร้างดังนี้:

type ชื่ออินเตอร์เฟซ interface {
    ชื่อเมทอด(รายการพารามิเตอร์) รายการประเภทการคืนค่า
}
  • ชื่ออินเตอร์เฟซ: ชื่อของอินเตอร์เฟซตามหลักการตั้งชื่อของ Go, เริ่มต้นด้วยตัวอักษรตัวใหญ่
  • ชื่อเมทอด: ชื่อของเมทอดที่จำเป็นต้องใช้โดยอินเตอร์เฟซ
  • รายการพารามิเตอร์: รายการพารามิเตอร์ของเมทอด โดยมีพารามิเตอร์ที่แยกด้วยเครื่องหมายจุลภาค
  • รายการประเภทการคืนค่า: รายการประเภทที่เมทอดจะคืนค่ากลับ

ถ้าชนิดหนึ่ง implement ทุกเมทอดในอินเตอร์เฟซ ดังนั้นชนิดนั้น implement อินเตอร์เฟซด้วย

type Worker interface {
    Work()
    Rest()
}

ในอินเตอร์เฟซ Worker ดังกล่าว ชนิดใดๆ ที่มี Work() และ Rest() เมทอดจะพอใจที่จะ implement อินเตอร์เฟซ Worker

3 กลไกการ implement อินเตอร์เฟซ

3.1 กฎสำหรับการ implement อินเตอร์เฟซ

ใน Go language, ประเภทหนึ่งจำเป็นต้องทำการ implement ทุกเมทอดในอินเตอร์เฟซเพื่อพิจารณาว่า implement อินเตอร์เฟซนั้นๆ กฎการ implement อินเตอร์เฟซมีดังนี้:

  • ชนิดที่ implement อินเตอร์เฟซสามารถเป็น struct หรือประเภทที่ปรับแต่งเองได้
  • หนึ่งชนิดต้องทำการ implement ทุกเมทอดในอินเตอร์เฟซเพื่อพิจารณาว่า implement อินเตอร์เฟซนั้น
  • เมทอดในอินเตอร์เฟซจำเป็นต้องมีลายเซ็นต์เมทอดที่เหมือนคือเมทอดในอินเตอร์เฟซที่กำลัง implement โดยรวมถึงชื่อ รายการพารามิเตอร์ และค่าคืนค่า
  • ชนิดหนึ่งสามารถ implement หลายอินเตอร์เฟซพร้อมกันได้

3.2 ตัวอย่าง: การ implement อินเตอร์เฟซ

ตอนนี้เรามาแสดงกระบวนการและเมทอดในการ implement อินเตอร์เฟซผ่านตัวอย่างเฉพาะ พิจารณาอินเตอร์เฟซ Speaker:

type Speaker interface {
    Speak() string
}

เมื่อต้องการให้ชนิด Human implement อินเตอร์เฟซ Speaker เราต้องกำหนดเมทอด Speak สำหรับชนิด Human:

type Human struct {
    Name string
}

// เมทอด Speak ทำให้ Human implement อินเตอร์เฟซ Speaker
func (h Human) Speak() string {
    return "สวัสดี ฉันชื่อ " + h.Name
}

func main() {
    var speaker Speaker
    james := Human{"เจมส์"}
    speaker = james
    fmt.Println(speaker.Speak()) // ผลลัพธ์: สวัสดี ฉันชื่อ เจมส์
}

ในโค้ดด้านบน, struct ชนิด Human ทำการ implement อินเตอร์เฟซ Speaker ด้วยการ implement เมทอด Speak() เราจะเห็นในฟังก์ชัน main ว่าตัวแปรชนิด Human ที่ชื่อ james ถูกกำหนดให้กับตัวแปรชนิด Speaker ที่ชื่อ speaker เพราะ james พอใจที่จะ implement อินเตอร์เฟซ Speaker

4 ผลประโยชน์และการใช้อินเตอร์เฟซ

4.1 ผลประโยชน์ของการใช้อินเตอร์เฟซ

มีหลายประโยชน์ในการใช้อินเตอร์เฟซ:

  • การถอดโครง: อินเตอร์เฟซทำให้โค้ดของเราถอดจากรายละเอียดของการปฏิบัติที่เจาะจง ทำให้โค้ดมีความยืดหยุ่นและการบำรุงรักษาที่ดีขึ้น
  • ความสามารถในการแทนที่: อินเตอร์เฟซทำให้เราสามารถแทนที่การปฏิบัติภายในได้อย่างง่ายดาย, ขอแต่ถ้าการปฏิบัติใหม่พอใจในอินเตอร์เฟซเดียวกัน
  • ความยืดหยุ่นในการขยาย: อินเตอร์เฟซทำให้เราสามารถขยายความสามารถของโปรแกรมได้โดยไม่ต้องปรับเปลี่ยนโค้ดเดิม
  • ความสะดวกในการทดสอบ: อินเตอร์เฟซทำให้การทดสอบหน่วยเป็นเรื่องง่าย เราสามารถใช้ออบเจกต์มอคเพื่อ implement อินเตอร์เฟซเพื่อการทดสอบโค้ด
  • พอลิโมรฟิซึม: อินเตอร์เฟซ implement พอลิมอร์ฟิซึม ทำให้ออบเจกต์ต่างๆ สามารถตอบสนองข้อความเดียวกันในสถานการณ์ต่างๆ ได้

4.2 สถานการณ์การใช้งานของอินเทอร์เฟซ

อินเทอร์เฟซถูกใช้อย่างแพร่หลายใน Go language ดังตัวอย่างของสถานการณ์การใช้งานที่ทั่วไปต่อไปนี้

  • อินเทอร์เฟซในไลบรารีมาตรฐาน: ตัวอย่างเช่น อินเทอร์เฟซ io.Reader และ io.Writer นั้นถูกใช้งานอย่างแพร่หลายสำหรับการประมวลผลไฟล์และโปรแกรมเครือข่าย
  • การเรียงลำดับ: การสร้างเมทอด Len() Less(i, j int) bool, และ Swap(i, j int) ในอินเทอร์เฟซ sort.Interface ช่วยให้สามารถเรียงลำดับของ slice ที่กำหนดเองได้
  • HTTP Handlers: การสร้างเมทอด ServeHTTP(ResponseWriter, *Request) ในอินเทอร์เฟซ http.Handler ช่วยให้สามารถสร้าง HTTP handlers ที่กำหนดเองได้

นี่คือตัวอย่างการใช้งานอินเทอร์เฟซสำหรับการเรียงลำดับ:

package main

import (
    "fmt"
    "sort"
)

type AgeSlice []int

func (a AgeSlice) Len() int           { return len(a) }
func (a AgeSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a AgeSlice) Less(i, j int) bool { return a[i] < a[j] }

func main() {
    ages := AgeSlice{45, 26, 74, 23, 46, 12, 39}
    sort.Sort(ages)
    fmt.Println(ages) // ผลลัพธ์: [12 23 26 39 45 46 74]
}

ในตัวอย่างนี้ โดยการสร้างเมทอดสามชุดของ sort.Interface เราสามารถเรียงลำดับ slice AgeSlice โดยแสดงถึงความสามารถของอินเทอร์เฟซในการขยายคุณสมบัติของชนิดที่มีอยู่ได้

5 คุณลักษณะขั้นสูงของอินเทอร์เฟซ

5.1 อินเทอร์เฟซว่างและการนำไปใช้

ใน Go language อินเทอร์เฟซว่างคือประเภทอินเทอร์เฟสที่ไม่มีเมทอดใด ๆ ดังนั้นเกือบทุกชนิดของค่าสามารถถือเป็นอินเทอร์เฟซว่าง อินเทอร์เฟซว่างถูกแทนด้วย interface{} และมีบทบาทสำคัญมากใน Go เป็นประเภทที่สามารถใช้งานได้อย่างยืดหยุ่นอย่างมาก

// กำหนดอินเทอร์เฟซว่าง
var any interface{}

การจัดการประเภทแบบไดนามิก:

อินเทอร์เฟซว่างสามารถเก็บค่าของชนิดใด ๆ ทำให้มีประโยชน์อย่างมากสำหรับการจัดการประเภทที่ไม่แน่นอน เช่น เมื่อคุณสร้างฟังก์ชันที่ยอมรับพารามิเตอร์ของชนิดต่าง ๆ อินเทอร์เฟซว่างสามารถใช้เป็นประเภทพารามิเตอร์ที่ยอมรับข้อมูลของชนิดใด ๆ

func PrintAnything(v interface{}) {
    fmt.Println(v)
}

func main() {
    PrintAnything(123)
    PrintAnything("hello")
    PrintAnything(struct{ name string }{name: "Gopher"})
}

ในตัวอย่างข้างต้น ฟังก์ชัน PrintAnything รับพารามิเตอร์ของชนิดอินเทอร์เฟซว่าง v และพิมพ์ออกมา PrintAnything สามารถจัดการว่าจะเป็นจำนวนเต็ม สตริง หรือสร้างตารางได้

5.2 การฝังอินเทอร์เฟส

การฝังอินเทอร์เฟซหมายถึงอินเทอร์เฟซหนึ่งมีเมตอดทั้งหมดของอินเทอร์เฟซอีกอินทรี เเละอาจเอ่มเมตอดใหม่ ๆ เข้าไปได้ การฝังอินเทอร์เฟซนั้นสามารถทําได้โดยการใส่อินเทอร์เฟซอื่นลงไปในการกําหนดอินเทอร์เฟซ

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// อินเทอร์เฟส ReadWriter ฝัง Reader และ Writer อินเทอร์เฟซ
type ReadWriter interface {
    Reader
    Writer
}

โดยการใช้การฝังอินเทอร์เฟซแบบนี้ เราสามารถสร้างโครงสร้างอินเทอร์เฟซที่ใช้งานได้อย่างยืดหยุ่นและมีลำดับ ในตัวอย่างนี้ อินเทอร์เฟซ ReadWriter รวมเมตอดของ อินเทอร์เฟซ Reader และ Writer สร้างความเชี่ยวชาญในการอ่านและเขียน

5.3 การตรวจสอบประเภทของอินเทอร์เฟซ

การตรวจสอบประเภทเป็นการดำเนินการเพื่อตรวจสอบและแปลงค่าของอินเทอร์เฟซ เมื่อพวกเราต้องการดึงค่าของประเภทเฉพาะออกจากอินเทอร์เฟซ แล้วการตรวจสอบประเภทกลายเป็นสิ่งที่สำคัญมาก

รูปแบบพื้นฐานของการตรวจสอบ:

value, ok := interfaceValue.(Type)

ถ้าการตรวจสอบประเภทประสบความสำเร็จ value จะเป็นค่าของประเภทหลัก Type และ ok จะเป็น true ถ้าการตรวจสอบประเภทล้มเหลว value จะเป็นค่าศูนย์ของประเภท Type และ ok จะเป็น false

var i interface{} = "hello"

// การตรวจสอบประเภท
s, ok := i.(string)
if ok {
    fmt.Println(s) // ผลลัพธ์: hello
}

// การตรวจสอบประเภทที่ไม่เป็นจริง
f, ok := i.(float64)
if !ok {
    fmt.Println("การตรวจสอบล้มเหลว!") // ผลลัพธ์: การตรวจสอบล้มเหลว!

สถานการณ์การใช้งาน:

การตรวจสอบประเภท ต่าง fold ใช้กันกว่าการตัดสินและแปลงประเภทของค่าในอินเทอร์เฟซว่าง interface{} หรือในกรณีของการดำเนินการหลายอินเทอร์เฟซ ที่จะดึงประเภทซึ่งใช้ดำเนินการแสดงตามอินเทอร์เฟซที่แนบมา

5.4 อินเทอร์เฟซและโพลีมอร์ฟิซึ่ง

โพลีมอร์ฟิซึ่งเป็นแนวคิดหลักในโปรแกรมเชิงวัตถุ ที่อนุญาตให้ชนิดข้อมูลต่าง ๆ ถูกประมวลผลอย่างเป็นรูปเดียวกันผ่านทางอินเทอร์เฟซเท่านั้น โดยไม่ต้องกังวลเรื่องชนิดที่เฉพาะเจาะจง ในภาษา Go อินเทอร์เฟซเป็นประการหลักในการบรรลุโพลีมอร์ฟิซึ่ง

การสร้างโพลีมอร์ฟิซึ่งผ่านทางอินเทอร์เฟซ

type Shape interface {
    Area() float64
}

type Rectangle struct {
    Width, Height float64
}

type Circle struct {
    Radius float64
}

// รูปสี่เหลี่ยมผันตัวทำการแยกอินเทอร์เฟซ
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// วงกลมทำการแยกอินเทอร์เฟซ
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// คำนวณพื้นที่ของรูปทรงต่าง ๆ
func CalculateArea(s Shape) float64 {
    return s.Area()
}

func main() {
    r := Rectangle{Width: 3, Height: 4}
    c := Circle{Radius: 5}
    
    fmt.Println(CalculateArea(r)) // ผลลัพธ์: พื้นที่ของรูปสี่เหลี่ยม
    fmt.Println(CalculateArea(c)) // ผลลัพธ์: พื้นที่ของวงกลม
}

ในตัวอย่างนี้ อินเทอร์เฟซ Shape กำหนดวิธี Area สำหรับรูปทรงต่าง ๆ ทั้งสองชนิดคือ Rectangle และ Circle ที่บรรลุอินเทอร์เฟซนี้ หมายความว่าชนิดเหล่านี้มีความสามารถในการคำนวณพื้นที่ ฟังก์ชัน CalculateArea รับพารามิเตอร์ชนิดอินเทอร์เฟซ Shape และสามารถคำนวณพื้นที่ของรูปทรงใด ๆ ที่บรรลุอินเทอร์เฟซ Shape

ด้วยวิธีนี้ เราสามารถเพิ่มชนิดของรูปทรงใหม่ได้อย่างง่าย ๆ โดยไม่จำเป็นต้องแก้ไขการปฏิบัติให้กับฟังก์ชัน CalculateArea นี้เป็นความยืดหยุ่นและสามารถขยายได้ที่โพลีมอร์ฟิซึ่งนำเสนอให้โค้ด