iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 17
0

難得有讀者主動點播,今天就來介紹「interface」的概念,中國翻譯為「接口」。要特別注意,這個interface跟其他語言中的定義與作用會不太一樣。

首先讓我們回憶一下golang的特性,會想起他是「輕量級的物件導向」,也就是他沒有完全實作物件導向的所有特徵。具體來說,golang沒有class(類別)與繼承(這樣還能稱之為物件導向嗎?)。但是現代軟體開發,如果需要類似「多型」的需求怎麼辦呢?interface就是golang中用來實踐多型的利器,雖然並不是完全符合多型的概念,但至少在概念上是接近的。尤其是在golang這種強型別的語言,interface可以發揮更高的潛能。

interface有兩種,分別是型態與定義。interface可以代表任何型態,這在開發上可以帶來極大的便利(弱型別語言表示),我們來看看具體上要如何實作:

func Hello(value interface{}) {  
}

將interface作為參數宣告,這個函數就可以接受任意型態的參數。但在實際使用之前,我們還是必須先辨別傳遞進來的參數型別,才能做接下來的邏輯實作,畢竟golang依然是個強型別的語言,沒有因為有了interface就做出讓步。

func Hello(value interface{}) {  
    // 透過型態斷言揭露 interface{} 真正的型態。
    switch v := value.(type) {
        // 如果 value 是字串型態。
        case string:
            fmt.Println("value 是字串,內容是 " + v)
        // 如果 value 是 int 型態。
        case int:
            fmt.Printf("value 是數值,加上二就是 %d", v + 2)
    }
}

延續第一個範例,我們從外部得到型別未知的參數value,透過switchvalue.(type)方法,可以將不同型別的邏輯分離出來,做不同的處理。如果你很確定參數的型別,也可以直接使用宣告的方式來取代switch判斷,方法如下:

func Hello(value interface{}) {  
    fmt.Println("value 是字串,內容是 " + value.(string))
}

但如果interface參數與你預期的型別不同,會出現panic警告。作為參數可以為任意類別,如果函數的返回值為interface,也就代表函數可以返回任意類別值。

在變數的方面,當我們定義一個空的interface,他可以指定為任意型別:

var a interface{}
var i int = 5
s := "Hello world"
// a可以儲存任意類別的值
a = i
a = s

到這邊你可能會想,一個強型別語言為什麼需要想辦法實作一個可以是任何型態的參數或變數,這不是根本否定的強型別的價值嗎?我想到一個長久以來在工程師圈關於「限制-自由」的拉扯,有一句名言是這樣總結的:

限制帶給你新的自由

限制的好處是當我們在規則與紀律之中妥協,我們可以更早發現不協調之處,讓bug無所遁形。如果毫無限制邊界,反而讓人無從發揮。我認為這並不是一個布林的問題,而是float,在光譜之中有眾多選擇,可以讓每個人依喜好與需求做選擇。

這樣的設計讓golang在大部份的時候受到型別拘束保護,不會產生型別的意外狀況;當在需要開發套件與第三方程式串接的時候,又不需要把自己綁死侷限了開發空間的可能,是語言設計者的一個優雅的權衡。

我們來看另一個例子,如何在golang中用interface實現多型:

package main

import (
	"fmt"
)

type Animal interface {
	Speak() string
}

type Dog struct {
}

func (d Dog) Speak() string {
	return "Woof!"
}

type Cat struct {
}

func (c Cat) Speak() string {
	return "Meow!"
}

type Pikachu struct {
}

func (p Pikachu) Speak() string {
	return "Pika pika!"
}

type Programmer struct {
}

func (j Programmer) Speak() string {
	return "Design patterns!"
}
func main() {
	animals := []Animal{Dog{}, Cat{}, Pikachu{}, Programmer{}}
	for _, animal := range animals {
		fmt.Println(animal.Speak())
	}
}

如果執行這段程式,我們會得到:

Woof!
Meow!
Pika pika!
Design patterns!

Animal作為一個interface定義一個空的Speak()方法,藉由宣告一個Animal陣列animals將貓、狗、皮卡丘、工程師實體傳進陣列中,接著實體各自執行自己實作的Speak()方法。

今天先說明到這,希望能多少有點幫助。

Reference


上一篇
Struct 結構
下一篇
Goroutine
系列文
啥物碗Golang? 30天就Go30

1 則留言

0
alfredisabug
iT邦新手 5 級 ‧ 2018-10-24 09:25:20

之前只知道interface可以怎麼用
但這篇有說出為什麼要這樣用以及原因
謝謝大神的講解

Bater iT邦新手 5 級‧ 2018-10-24 20:52:02 檢舉

我也是邊寫邊學習啦,有幫助到你真是太好了

我要留言

立即登入留言