iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0
Modern Web

就是個Go,我也可以啦!GOGO系列 第 17

2023鐵人賽Day 17 Go 方法集合決定介面實現

  • 分享至 

  • xImage
  •  

我一開始對於介面這東西其實是非常疑惑的,不懂為什麼需要這個概念,因為我寫的是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的集合

Go 語言的一個創新就是當你有一個“規則清單”(介面),裡面列出了幾件事情或動作(方法),如果有一個“玩具”(類型)能做到清單上的所有事情,那麼我們就說這個玩具遵從了那個清單,即使玩具可以做更多的事,只要它能做到清單上的所有事,它就算遵從

重點是,這個玩具不需要有一個標籤說它遵從這個清單,只要它的功能能夠匹配,就好了

像是java跟typescript,需要implements ToyChecklist
而在 Go 中,這個連接過程是隱式的,它就像一種 "膠水",將類型和介面黏合在一起,當我們說一個類型 "實現" 了某個介面,意味著這個類型有接口所要求的所有方法。這就是所謂的方法集合。

怎麼做到方法的集合

  1. 先定義一個方法集合列表 (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)關聯。 | 任何類型都可以實現一個介面。 |
      實現 | 當你為一個類型添加方法時,這個類型就有了方法集合。 | 一個類型如果有介面所需的所有方法,就實現了該介面。 |

  2. 定義方法,並套用接收者

    • 值接收者: 如果方法使用值接收者(例如 (t MyType)),那麼這個方法會屬於 MyType 的方法集合
    • 指針接收者: 如果方法使用指針接收者(例如 (t *MyType)),那麼這個方法會屬於 *MyType(MyType的指針)的方法集合。但需要注意的是,即使是用指針接收者定義的方法,它仍然可以在值上被調用,只要該值是可尋址的

確認方法的集合

我們可以使用以下的方法來取得方法的集合

// 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的動態及靜態特性

介面的靜態特性:

  1. 介面類型的變數具有靜態類型。例如,var e error 中變數 e 的靜態類型為 error

  2. 編譯器在編譯階段進行類型檢查。當一個介面類型變數被賦值時,編譯器會檢查右值的類型是否實現了該介面方法集合中的所有方法。

    // 定義一個介面,有一個方法叫做 "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!
    }
    
    

介面的動態特性:

  1. 介面類型變數具有動態類型。這表示在運行時,儲存在接口類型中的變數的真實類型可以被確定。例如,var i interface{} = 13 中介面變數 i 的動態類型為 int
  2. 介面類型變數在程序運行時可以被賦值為不同的動態類型的變數,從而支持運行多態
    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,我也不知道要寫多少才能習慣,但跨出這一步後代表我要習慣它,一起加油吧


上一篇
2023鐵人賽Day 16 Go x 探討方法本質及RECEIVER
下一篇
2023鐵人賽Day 18 Go 探索併發的魅力: 為何我們需要它以及如何入門
系列文
就是個Go,我也可以啦!GOGO30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言