我一開始對於介面這東西其實是非常疑惑的,不懂為什麼需要這個概念,因為我寫的是ruby,ruby是動態語言,變數類型是在運行時才決定的,而不是在編譯時(事實上,這些語言通常是解釋型的,沒有傳統的編譯過程),故我並不需要先確定他的型別才能繼續寫
這對我來說也是一個新的挑戰,理解介面以及善於使用它
介面的基礎可以詳 2023鐵人賽Day 6 Go X 介面
這幾天我們要來循序漸進地談介面
像是java我們會這樣做
// 這是一份“玩具清單”,列出玩具應該有的功能
interface ToyChecklist {
void play(); // 玩具應該能玩
}
// 這是一個“玩具”名稱為TeddyBear(泰迪熊)
class TeddyBear implements ToyChecklist {
@Override
public void play() {
// 泰迪熊玩的方法
}
}
如果是typescript的話
// 這是一份“玩具清單”,列出玩具應該有的功能
interface ToyChecklist {
play(): void; // 玩具應該能玩
}
// 這是一個“玩具”名稱為TeddyBear(泰迪熊)
class TeddyBear implements ToyChecklist {
play() {
// 泰迪熊玩的方法
console.log("TeddyBear is playing!");
}
}
let myBear: ToyChecklist = new TeddyBear();
myBear.play(); // 輸出: TeddyBear is playing!
先定義一個介面,這個介面會有需要實現的方法,此時也需要一個類別,這個類別明確的指定實現這個介面,並於類別內實現這個方法
那Go呢,Go就不太依樣了
Go 語言的一個創新就是當你有一個“規則清單”(介面),裡面列出了幾件事情或動作(方法),如果有一個“玩具”(類型)能做到清單上的所有事情,那麼我們就說這個玩具遵從了那個清單,即使玩具可以做更多的事,只要它能做到清單上的所有事,它就算遵從
重點是,這個玩具不需要有一個標籤說它遵從這個清單,只要它的功能能夠匹配,就好了
像是java跟typescript,需要implements ToyChecklist
而在 Go 中,這個連接過程是隱式的,它就像一種 "膠水",將類型和介面黏合在一起,當我們說一個類型 "實現" 了某個介面,意味著這個類型有接口所要求的所有方法。這就是所謂的方法集合。
先定義一個方法集合列表 (Method List)
方法集合不是interface
喔,不要誤會!!
方法集合描述的是某個具體類型所擁有的所有方法,每個非介面類型interface
都有其相對應的方法集合
當你為struct或其他自定義類型添加方法時,你實際上是在擴充該類型的方法集合
type Dog struct {}
func (d Dog) Bark() {
fmt.Println("Woof!")
}
func (d Dog) Run() {
fmt.Println("The dog is running!")
}
在上述例子中,Dog 的方法集合包含 Bark 和 Run
我們必須清楚的分辨介面及集合有什麼差別!
比較項目 | 方法集合 | 介面 |
---|
定義 | 一個類型所擁有的所有方法的集合。 | 定義了一組方法但不提供實現。 |
範例 | type Dog struct {} func (d Dog) Bark() {}
| type Animal interface { Speak() }
|
用途 | 描述一個具體類型可以做什麼。 | 描述一個類型應該有哪些方法。 |
與類型的關係 | 與具體的類型(例如 struct
)關聯。 | 任何類型都可以實現一個介面。 |
實現 | 當你為一個類型添加方法時,這個類型就有了方法集合。 | 一個類型如果有介面所需的所有方法,就實現了該介面。 |
定義方法,並套用接收者
我們可以使用以下的方法來取得方法的集合
// DumpMethodSet 輸出一個 interface 值的方法集合。
// 它使用反射 (reflection) 來動態檢查傳入的值有哪些方法。
func DumpMethodSet(i interface{}) {
// 取得 i 的反射類型
v := reflect.TypeOf(i)
// 由於 i 可能是一個指針,所以我們需要取得它所指向的元素的類型。
// 例如,如果 i 是一個 *TeddyBear(指向 TeddyBear 的指針),則 elemTyp 將是 TeddyBear。
elemTyp := v.Elem()
// 獲取 elemTyp 的方法數量
n := elemTyp.NumMethod()
// 如果沒有方法,輸出 "empty" 並返回
if n == 0 {
fmt.Printf("%s's method set is empty!\n", elemTyp)
return
}
// 輸出方法集合的開頭
fmt.Printf("%s's method set:\n", elemTyp)
// 迴圈遍歷所有的方法
for j := 0; j < n; j++ {
// 獲取第 j 個方法
method := elemTyp.Method(j)
// 輸出該方法的名稱
fmt.Printf("Method: %s\n", method.Name)
}
// 輸出空行作為結尾
fmt.Printf("\n")
}
// 出處:Go語言的精進之路
Go 語言不需要類型明確聲明它實現了哪些介面,只要類型的方法與介面相匹配,它就自動實現了該介面,使用DumpMethodSet,你可以看到實際的方法和介面的方法如何匹配
我們把ToyChecklist 帶進去看看
type ToyChecklist interface {
play()
}
type TeddyBear struct{}
func (t TeddyBear) play() {
fmt.Println("TeddyBear is playing!")
}
func main() {
var toy ToyChecklist = TeddyBear{}
DumpMethodSet(&toy)
}
這個會輸出
❯ go run main.go
main.ToyChecklist's method set:
Method: play
上面是一個小工具,你可以看清楚他實現了什麼方法
Go 的介面魅力在哪裡,上面我們都是在介紹go的靜態特性,但你知道嗎,go也可以如ruby/python那樣使用動態特性的鴨子類型喔,我們先來整理一下go的動態及靜態特性
介面的靜態特性:
介面類型的變數具有靜態類型。例如,var e error
中變數 e
的靜態類型為 error
。
編譯器在編譯階段進行類型檢查。當一個介面類型變數被賦值時,編譯器會檢查右值的類型是否實現了該介面方法集合中的所有方法。
// 定義一個介面,有一個方法叫做 "Say"
type Talker interface {
Say() string
}
// 定義一個結構體 "Dog"
type Dog struct {}
// 為 "Dog" 結構體實現 "Say" 方法
func (d Dog) Say() string {
return "Woof!"
}
func main() {
var t Talker
t = Dog{} // 這裡是合法的,因為 "Dog" 類型實現了 "Talker" 介面的 "Say" 方法
fmt.Println(t.Say()) // 輸出: Woof!
}
介面的動態特性:
var i interface{} = 13
中介面變數 i
的動態類型為 int
package main
import (
"fmt"
)
func main() {
var i interface{}
i = 42
fmt.Printf("Value: %v, Type: %T\n", i, i) // 輸出: Value: 42, Type: int
i = "hello"
fmt.Printf("Value: %v, Type: %T\n", i, i) // 輸出: Value: hello, Type: string
i = true
fmt.Printf("Value: %v, Type: %T\n", i, i) // 輸出: Value: true, Type: bool
}
介面的理解對我來說其實非常的吃力,習慣了ruby的寫法,實在不知道該怎麼理解interface,我也不知道要寫多少才能習慣,但跨出這一步後代表我要習慣它,一起加油吧