Go起手式之四


Posted by Nacho on 2020-04-26

在Go的世界,你可以先定義一些方法或形態,然後交給其他函式來實作。這種定義先行的設計方式就叫做Interfaces(介面)。概念有點像工作媒合,企業為某職缺開出職能需求,但能實際執行的不是職缺是來應徵合乎條件的求職者。也就是說職能需求成了介面,求職者負責將介面的內容實作(之後看一些例子會更明白)。

目標

  • 認識Interfaces的概念,大致分成兩部分"針對方法"和"針對型別"
  • 針對方法的部分,這裡會瞭解到如何定義type interface及調用interface
  • 針對型別的部分,這裡會瞭解到 Empty Interfaces 的觀念,也就是不事先宣告型別讓變數值來決定

針對方法 => Interfaces

這裡所指的介面(Interfaces)不是圖形化的那種,而是可以想像職缺與求職者那樣的關係。比方有個大廚的職缺,定義主廚要具備的能力,來應徵大廚的人就是要滿足這些能力並實際執行的人。換言之:

  • 介面(Interfaces)只負責定義方法(Method),如大廚的職缺指定義能力
  • 介面裏的方法實作交給其他要用到的結構來完成,例如大廚負責執行這些能力
  • 此外,使用介面不需要關鍵字的幫助(例如其他程式語言會用implements)可以直接用(Interfaces are implemented implicitly)

舉個實際的例子,這有兩個結構分別是 cat 跟 snak,若要形容個別動物的特徵,我可以定義一個介面並宣告裡面有一個description的方法,並讓cat、snack各自完成實作。如果後續還加入了其他的動物,並要描述它的特徵,我便可以繼續沿用這個介面。

type animal interface {
  description() string
}

type cat struct {
  Type  string
  Sound string
}

type snake struct {
  Type      string
  Poisonous bool
}
// 實作描述snake的方法,帶入snake的結構 
func (s snake) description() string {
  return fmt.Sprintf("Poisonous: %v", s.Poisonous)
}
// 實作描述cat的方法,帶入cat的結構 
func (c cat) description() string {
  return fmt.Sprintf("Sound: %v", c.Sound)
}

func main() {
  var a animal
  a = snake{Poisonous: true}
  fmt.Println(a.description()) // 
  a = cat{Sound: "Meow!!!"}
  fmt.Println(a.description()) //
}

另一個Example,定義了一個geometry(幾何)的介面與兩個結構rect(矩形)和circle(圓),並為兩個結構實作介面的方法。實務上會設計一個調用函式來為不同的結構調用介面方法,這裡設計一個measure來印出變數 r 和 c 的計算結果。Go by Example: Interfaces

package main

import (
    "fmt"
    "math"
)

type geometry interface {
    area() float64
    perim() float64
}

type rect struct {
    width, height float64
}
type circle struct {
    radius float64
}

func (r rect) area() float64 {
    return r.width * r.height
}
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}

func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}

func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}

func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}

    measure(r) // {3 4} => g
               // 12    => g.area()
               // 14    => g.perim()
    measure(c) // {5}               => g
               // 78.53981633974483 => g.area()
               // 31.41592653589793 => g.perim()
}

針對型別 => Empty Interfaces

通常會使用type與interface來定義介面,這個概念是前面提到的方法介面。

type Fruit interface {
    // 數組方法
}

然而,當方法介面裏頭沒有定義任何方法和type時,它就是空介面(Empty Interfaces)

空介面

  • 空介面的表示方式Interface {}
  • 一個空介面可能接受任何型別的值(int、string、結構等)
  • 程式設計中用來處理未定義型別的值,例如 fmt.Printf 能依據變數的值自動判斷空介面的型別

Example: 宣告空介面 i ,指派不同值並用fmt.Printf印出值與型別

func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i) // 印出值與型別
}

func main() {
    // 宣告一個空介面
    var i interface{}
    describe(i) // (<nil>, <nil>)

    i = 42
    describe(i) // (42, int)

    i = "hello"
    describe(i) // (hello, string)
}

型別斷言(Type assertions)

  • 三種斷言方式
  • 方法一: t := i.(T)
    • 幫助空介面指出 T 是否是變數i的正確型別
    • 如果正確,回傳值 t (回傳變數i的值)
    • 不正確話,拋出panic,中斷程式執行
  • 方法二: t, ok := i.(T)
    • 回傳值 t (回傳變數i的值)
    • 回傳值 ok (判斷是否為變數真正之型別)
    • 不中斷程式執行

Example: 檢查變數i的型別

func main() {
    var i interface{} = "hello" // 給定字串值,現在正確型別為字串

    s := i.(string)
    fmt.Println(s) // hello

    s, ok := i.(string)
    fmt.Println(s, ok) // hello true

    f, ok := i.(float64)
    fmt.Println(f, ok) // 0 false

    f = i.(float64) // 程式執行中斷
    fmt.Println(f) // panic: interface conversion: interface {} is string, not float64
}
  • 分法三: 稱作type switch用於區分多種型別
    • 與一般switch條件判斷一樣,只是case判斷的條件是型別不是變數值
    • 變數值的處裡要寫在case裏頭
    • 斷言方式與方法一個概念t := i.(T)相同,但是 T 要改為type關鍵字
switch v := i.(type) {
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}

Example: 用do()來判斷值的型別

func do(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
        fmt.Printf("%q is %v bytes long\n", v, len(v))
    default:
        fmt.Printf("I don't know about type %T!\n", v)
    }
}

func main() {
    do(21)      // Twice 21 is 42
    do("hello") // "hello" is 5 bytes long
    do(true)    // I don't know about type bool!
}

參考

官網教學
Golang - Interfaces(Leon's Blog => 補充不少實作與解釋的部分)
Go by example


#Go Interface #Empty Interface







Related Posts

MTR04_1119

MTR04_1119

【JS基礎】字串處理

【JS基礎】字串處理

React(13) - useContext & context API

React(13) - useContext & context API


Comments