PJCHENder 未整理筆記

[Golang] Interfaces

2020-06-05

[Golang] Interfaces

此篇為各筆記之整理,非原創內容,資料來源可見文末參考資料。

TL;DR

  • interface 的概念有點像是的藍圖,先定義某個方法的名稱(function name),會接收哪些參數及型別(list of argument type)、會回傳的值與型別(list of return types)。定義好藍圖之後,並不去管實作的細節,實作的細節會定義會由每個型別自行定義實作(implement)

Imgur

1
2
3
4
5
6
7
8
9
// 任何型別,只要符合定義規則的話,就可以被納入 bot interface 中
type bot interface {
// getGreeting 這個函式需要接收兩個參數(string, int),並回傳 (string, error) 才符合入會資格
getGreeting(string, int) (string, error)

// getBotVersion 這個函式需要回傳 float 才符合入會資格
getBotVersion() float64
respondToUser(user) string
}

Interface 是什麼?

interface 可以被想成是帶有 (value, type) 的元組(tuple),當我們呼叫某個 interface value 的方法時,實際上就是將該 value 去執行與該 type 相同名稱的方法(method)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// https://tour.golang.org/methods/11

type I interface {
M()
}

type T struct {
S string
}

func (t *T) M() {
fmt.Println(t.S)
}

type F float64

func (f F) M() {
fmt.Println(f)
}

func main() {
var i I

i = &T{"Hello"}
describe(i) // (&{Hello}, *main.T)
i.M() // 意思是將 type T 對應的 value (&{Hello}) 來執行 type T 對應的 M 方法

i = F(math.Pi)
describe(i) // (3.141592653589793, main.F)
i.M() // 意思是將 type F 對應的 value (3.1415) 去執行 type F 對應的 M 方法
}

func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}

Interfaces 試圖解決的問題

問題:共用相同邏輯但帶入不同型別參數的函式

如果某一個函式內部運作的邏輯相同,我們是否還需要只因為參數型別的不同而撰寫不同的 function 呢?舉例來說,同樣是 shuffle 這個方法,是否會因為傳入的型別不同,而需要建立多種不同的函式?

Interfaces 的使用

❓ 一個 type 能否屬於多個 interface

在程式中的任何 type,只要這個 type 的函式(function receiver)有符合到該 interface 的定義,就可以歸類到該 interface 底下

範例一

在這個例子中,square type 的方法 area() 因為符合 shape interface 的定義,所以 square type 也一併被歸類在 shape interface 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// STEP 3:定義 shape 這個 interface
// 因為 square type 的 area method 符合 shape interface 的規範
// 所以 square type 會被歸類在 shape interface 內
type shape interface {
area() int
}

// STEP 1:定義 square type
type square struct {
sideLength int
}

func main() {
// STEP 5:定義 square type 的變數
ten := square{sideLength: 10}

// STEP 6:printArea 中可以帶入 shape type
// 因為 square type 現在屬於 shape interface,所以可以放入 printArea 這個 function
printArea(ten)
}

// STEP 4:printArea 這個 function 可以帶入 shape interface 作為參數
func printArea(s shape) {
fmt.Println(s.area())
}

// STEP 2:定義 area 這個 square type 的 methods
func (s square) area() int {
return s.sideLength * s.sideLength
}

範例二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// STEP 1:宣告兩個 struct type
type englishBot struct{}
type spanishBot struct{}

// STEP 2:定義 bot type,它本質上是 interface
type bot interface {
// 在程式中的任何 type,只要是名稱為 getGreeting 而且回傳 string 的函式
// 將自動升級變成 "bot" 這個 type 的成員
getGreeting() string
}

func main() {
eb := englishBot{}
sb := spanishBot{}

// STEP 6:現在 eb 和 sb 都算是 bot type
printGreeting(eb)
printGreeting(sb)
}

// STEP 5:printGreeting 可以傳入 bot interface
func printGreeting(b bot) {
fmt.Println(b.getGreeting())
}

// STEP 3:此 receiver function 名稱為 getGreeting 且回傳 string,因此屬於 bot type
func (englishBot) getGreeting() string {
// VERY custom logic for generating an english greeting
return "Hi There!"
}

// STEP 4:此 receiver function 名稱為 getGreeting 且回傳 string,因此屬於 bot type
func (spanishBot) getGreeting() string {
return "Hola!"
}

以上面的程式碼為例:

  • type bot interface{} 中說明了主要名稱為 getGreeting 而且回傳值為 string 的話,都屬於 bot 這個 type
  • func (englishBot) getGreeting() string 符合了 bot 的標準,同時 type englishBot struct 也和這個 getGreeting 關聯再一起,所以一同升級為 bot type 的成員
  • 同理,type spanishBot struct 也一同升級成 bot type 的成員

Imgur

範例三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// STEP 1: 函式名稱為 Abs 且回傳 float64 即屬於 Abser type
type Abser interface {
Abs() float64
}

// STEP 2:定義一個 MyFloat type 且其 receiver function 符合 Abser interface 的規範
// MyFloat 會屬於 Abser type
type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

// STEP 3:定義一個 Vertex type 且其 receiver function 符合 Abser interface 的規範
// Vertex 會屬於 Abser type
type Vertex struct {
X, Y float64
}

func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}

a = f // MyFloat 可以 implement Abser type
fmt.Println(a.Abs()) // 1.4142

a = &v // *Vertex 可以 implement Abser type
fmt.Println(a.Abs()) // 5

// Cannot use 'v' (type Vertex) as type Abser.
// Vertex 不能 implement Abser type,因為 Abs 這個方法有 pointer receiver
a = v
}

io.Reader 這個 interface 為例

如果它不是 interfaces 的話,為了輸出各種不同的 input,會需要建立多個接受不同型別作為參數的 function:

Imgur

透過 Interface 的使用,將可以簡化成下面這樣:

Imgur

Interface 會隱性的被 implement

一個 Type 可以透過實作(implement)某一 interface 中的方法來實踐該 interface。舉例來說:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type I interface {
M()
}

type T struct {
S string
}

// 這個方法指的是 type T 會實作 interface I
// 但並不需要開發者主動去宣告它
func (t T) M() {
fmt.Println(t.S)
}

func main() {
// i 是 type T 但實作了 interface I
var i I = T{"hello"}
i.M()
}

透過 Interface,某一個變數可以執行多個不同 type 可以執行的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

type I interface {
M()
}

type T struct {
S string
}
// type I 也將屬於 interface I
func (t T) M() {
fmt.Println(t.S)
}

type F float64
// type F 也將屬於 interface I
func (f F) M() {
fmt.Println(f)
}

func main() {
// i 是 interface I
// i 將可以使用 type F 和 type I 的方法
var i I

i = &T{"Hello"}
describe(i) // (&{Hello}, *main.T)
i.M()

i = F(math.Pi)
describe(i) // (3.141592653589793, main.F)
i.M()
}

func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}

而一個 interface 的值可以想成是由 (value, type) 組成的 tuple,當我們在呼叫某一 interface 的方法時,它會根據這個 type 來執行以上面來說,

使用 Interface 建立自己的 function

Stringer Interface 為例

Stringer @ golang

可以看到原本的 Stringer Interface 長這樣:

1
2
3
type Stringer interface {
String() string
}

也就是說,任何 Type 底下只要有 String() 這個方法且回傳 string,都會被歸到 Stringer interface。

幫 Person type 客製化自己的 String 方法

因此,我們可以建立一個 type,並為它添加 String() 方法後,它就會被歸類在 Stinger interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Person struct {
Name string
Age int
}

// Person 這個 type 有 String 方法,且會回傳 string,因此可被歸在 Stringer interface
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
a := Person{"Arthur", 42} // ({Name:Arthur Age:42}, main.Person)
z := Person{"Zaphod Beeblebrox", 9001} // ({Name:Zaphod Beeblebrox Age:9001}, main.Person)

fmt.Println(a, z) // 會呼叫到 a.String() 和 z.String()
// Arthur (42 years) Zaphod Beeblebrox (9001 years)
}

幫 IPAddr type 客製化自己的 String 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// A Tour of Go: Exercise: Stringers
// https://tour.golang.org/methods/18

type IPAddr [4]byte

// String() string 符合 Stringer interface
func (ip IPAddr) String() string {
var ips []string
for _, ipNumber := range ip {
ips = append(ips, strconv.Itoa(int(ipNumber)))
}
return strings.Join(ips, ",")
}

func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}

for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}

Writer Interface 為例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// STEP 1:建立一個 logWriter 的 type
type logWriter struct{}

func main() {
resp, err := http.Get("https://pjchender.github.io")
if err != nil {
fmt.Printf("Error: %v", err)
os.Exit(1)
}

// STEP 3:建立 logWriter
lw := logWriter{}

// STEP 4:因為 logWriter 已經歸類在 Writer Interface,所以可以帶入 io.Copy 內
io.Copy(lw, resp.Body)
}

// STEP 2:根據 Writer Interface 的定義(https://golang.org/pkg/io/#Writer)
// 來撰寫 logWriter 的 Write function
// 如此,它將會被歸類在 Writer Interface 內
func (logWriter) Write(bs []byte) (int, error) {
fmt.Println(string(bs))
fmt.Println("Just wrote this many bytes: ", len(bs))
return len(bs), nil
}

其他

empty interface

沒有定義任何方法的 interface 稱作 empty interface,它的值會是 any type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type I interface{}

func main() {
var i I
describe(i) // (<nil>, <nil>)

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

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

}

func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}

Type assertions

前面有提到,interface 可以想成是 (value, type) 的元組,透過 type assertion 則提供了一種方式可以存取該 interface value 底層的 concrete value:

1
2
3
4
5
6
7
// 斷定 interface 的 concrete type 是 T,並將 T 的 value 指派到變數 t
t := i.(T) // 如果型別不正確會直接 panic

// 如果要檢測某 interface 是否包含某一個 type,則需要接收兩個回傳值- underlying value 和 assertion 是否成功
t, ok := i.(T)
// 如果 i 有 T,則 t 會得到 underlying value,而 ok 會是 true
// 如果 i 沒有 T,則 t 會得到 type T 的 zero value,且 ok 會是 false

Type switches

type switches @ A Tour of Go

type switch 和一般的 switch 語法相同,只是 switch 判斷的內容是使用 type assertion(i.(type))、在 case 的地方則是判斷某一 interface value 的型別:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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() {
var i interface{}
i = "Hello"
fmt.Printf("(%v, %T)", i, i) // (Hello, string)

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

參考

Tags: Go

掃描二維條碼,分享此文章