เวลาเราต้องการใช้ interface ส่วนใหญ่เราจะสร้าง type ชื่อใหม่สำหรับ inteface แล้วค่อยเอา type ชื่อใหม่นั้นไปใช้ ประกาศตัวแปร หรือกำหนด parameter ของ function/method แต่จริงๆเราสามารถใช้งาน interface ได้เลยโดยไม่ต้องสร้าง type ใหม่ได้เช่นกัน
Type Literal
จาก spec ของ Go https://golang.org/ref/spec#Types บอกเอาไว้เกี่ยวกับ Type Literal เอาไว้ว่า
A type may be denoted by a type name, if it has one, or specified using a type literal, which composes a type from existing types.
TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType.
จากที่เห็นในลิสต์นี้ ทั้งหมดนี่คือ type ที่เราเอามาใช้งานแบบ type literal ได้ ซึ่งก็รวมถึง InterfaceType ด้วยนั่นเอง
ประกาศตัวแปรโดยใช้ Interface type literal
เวลาประกาศตัวแปรของ interface type literal เราก็แค่ยก interface block แบบที่เราเคยใช้กับตอน ประกาศ type นั่นล่ะ มาใช้ได้เลย เช่น
var finder interface {
Find(keyword string) []result
}
เท่านี้ตัวแปร finder ก็จะเป็นตัวแปรของ type interface ที่มี 1 method คือ Finder(keydord string) []result
แล้ว
หรือมีจุดอื่นที่เราต้องการใช้งาน interface type เช่นตอนทำ type assertion เราก็สามารถยก type literal ไปใช้ได้เช่นกันโดยไม่ต้องสร้าง type ชื่อใหม่ เช่น
finder, ok := f.(interface { Finder(string) []result })
ตัวอย่างจาก standard package errors
package errors
https://pkg.go.dev/errors นั้นมี concept ใหม่คือเรื่อง Unwrap
error ซึ่ง document เขียนเอาไว้ว่า
The
Unwrap
,Is
andAs
functions work on errors that may wrap other errors. An error wraps another error if its type has the methodUnwrap() error
คือ Unwrap
, Is
และ As
functions ใน package errors นั้นทำงานกับ error type ที่มี method Unwrap() error
บอกแบบนี้นั่นก็คือ type ที่ implements method Unwrap() error
นั่นละ แต่ว่าถ้าเราดู type ที่อยู่ใน document ของ errors
จะพบว่าเราไม่เจอ interface type สำหรับ Unwrap() error
method เลย
แบบนี้แล้ว function Unwrap
, Is
และ As
มันจัดยังไงนะ ลองเข้าไปดูโค้ดของสามฟังก์ชันนี้กัน
เริ่มที่ Unwrap
func Unwrap(err error) error {
u, ok := err.(interface {
Unwrap() error
})
if !ok {
return nil
}
return u.Unwrap()
}
จะเห็นว่า ใช้ type assertion แต่ว่าตรงที่อยู่ใน ()
หลัง err.
นั้นไม่ใช่ชื่อ type โดยตรง แต่เป็นการใช้ interface type literal นั่นเอง
ถัดไปดูที่ Is
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
if err = Unwrap(err); err == nil {
return false
}
}
}
จะเห็นว่าก็มีจุดที่ใช้ type assertion พร้อมกับใช้ interface type literal เช่นกัน แต่เป็นการเช็คว่า type implements method Is(error) bool
หรือเปล่า
ต่อไปก็ดูที่ As
func As(err error, target interface{}) bool {
if target == nil {
panic("errors: target cannot be nil")
}
val := reflectlite.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflectlite.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
targetType := typ.Elem()
if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
for err != nil {
if reflectlite.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflectlite.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
return true
}
err = Unwrap(err)
}
return false
}
ก็เหมือนกับสองอันก่อนหน้านี้แต่คราวนี้คือเช็คว่า type implements method As(interface{}) bool
หรือเปล่า นั่นเอง
ทำไมเขาถึงเลือกใช้ท่านี้กันนะ 🤔
คิดว่าอาจจะเป็นเพราะ interface ที่จะใช้งานนั้นมีแค่ method เดียว แล้วก็ไม่ได้ใช้ที่อื่นเลยยกเว้นแค่ในตัวฟังก์ชันเอง
กับสิ่งที่ทำให้ทำแบบนี้ได้ก็เพราะ Go interface นั้นคน implements ไม่จำเป็นต้องรู้ type ของ interface ของแค่มี methods ตรงตาม spec แค่นั้นเอง
อ้าวแล้วถ้าคน implements ไม่ได้ implement method ที่ต้องการไว้ล่ะ
ก็เป็นหน้าที่ของฝั่งคนใช้ interface ในกรณีนี้คือ 3 ฟังก์ชันนี้ ต้องเขียน logic จัดการเอาไว้ว่าถ้า implement จะทำยังไง ถ้าไม่ implement จะทำยังไง ซึ่งก็ใช้ type assertion นั่นเอง
อาจจะดูแปลกๆเพราะคัดกับภาษาที่คน implement ต้องระบุชัดเจนว่าจะ implement interface ไหนบ้าง เช่น (Java, C#)
เพราะหลักการของ Go นั้น คนที่ต้องการใช้งาน interface ต้องเป็นคนระบุ spec เอง ในเคสนี้คือฟังก์ชัน Unwrap
, Is
และ As
ทั้ง 3 ฟังก์ชันนี้ใช้ Interface เพื่อให้การทำงานของตัวเองยืดหยุ่นพอที่จะทำงานกับหลายๆ type ได้ ขอแค่ type นั้น implements method ตามที่ต้องการ
Top comments (0)