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
นี้เป็นความยืดหยุ่นและสามารถขยายได้ที่โพลีมอร์ฟิซึ่งนำเสนอให้โค้ด