iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 29
1
Modern Web

從無到有,使用 Go 開發應用程式系列 第 29

Interface

介面(interface)跟一般 Java 所熟知的介面意義是一樣的:定義實體(instance)的行為。

定義與實作

介面定義方法很簡單,只要定義傳入與傳出就行了,比方說 Generator 有個行為叫 Name ,我們可以這樣定義介面:

type Namer interface {
	Name(gender string, firstNameNum int) (name string, err error)
}

介面要如何實作?只要實體有實作這個介面就行了,不管是正常的 Generator 實作,或是一隻鴨子 Duck

type Generator struct {
	rand     *rand.Rand
	Resource NamesResource
}

func (generator *Generator) Name(gender string, firstNameNum int) (name string, err error) {
	switch gender {
	case "":
		name = generator.LastName() + generator.firstName(firstNameNum)
	case "male":
		name = generator.LastName() + generator.firstNameMale(firstNameNum)
	case "female":
		name = generator.LastName() + generator.firstNameFemale(firstNameNum)
	default:
		err = errors.New(fmt.Sprintf("gender '%s' is invalid", gender))
	}

	return
}

type Duck struct {}

func (generator Duck) Name(gender string, firstNameNum int) (name string, err error) {
	return "我是隻醜小鴨", nil
}

多型

介面也是一種型態,所以 Slice 或 Array 我們可以定義型態是介面,如:

var namer1 Namer = Duck{}
var namer2 Namer = &Generator{}

arr := []Namer{
    namer1,
    namer2,
}

fmt.Println(arr)

這裡必須注意,因為 Generator 實作的時候是使用傳址,所以介面這邊是要使用 & 取位址; Duck 則是傳值,所以不需要使用 & 取位址。

這邊會發現,我們可以丟很多不一樣的實體到一個 Slice 裡,只要有實作 Namer 就能丟。那有沒有一個型態是可以丟所有的實體?有的,它叫 interface{}

arr := []interface{}{
    Duck{},
    &Generator{},
    123,
    func(){},
}

fmt.Println(arr)

事實上, fmt.Println() 的參數,其實也是定義 interface{} ,所以才能接所有種類的參數。

判斷型別與轉型

比方說下面這段程式,定義了一個 slice 裡面有一層 slice ,但執行後會發現它出錯了:

arr := []interface{}{
    []int{1, 2, 3},
}

fmt.Println(arr[0])     // [1 2 3]
fmt.Println(arr[0][0])  // invalid operation: arr[0][0] (type interface {} does not support indexing)

它說 interface{} 不支援索引取值。

其實這是強型態的特色,宣告 interface{} ,它就把這個變數當成是 interface{} ,因此它不能當作 slice 操作。除非要拜託它把變數當成 slice :

arr := []interface{}{
    []int{1, 2, 3},
}

fmt.Println(arr[0])             // [1 2 3]
fmt.Println(arr[0].([]int)[0])  // 1

var.(type) 這是轉型的操作。如果可行的話,它會轉型成 type ,然後後面就可以繼續接 type 能做的操作。

它跟 Map 一樣會回傳第兩個值是 ok :

arr := []interface{}{
    []int{1, 2, 3},
    func() string { return "func" },
}

fmt.Println(arr[0].([]int)[0])  // 1
fmt.Println(arr[1].([]int)[0])  // cannot call non-function arr[1].([]int) (type []int)

因此可以這樣處理

arr := []interface{}{
    []int{1, 2, 3},
    func() string { return "func" },
}

fmt.Println(arr[0].([]int)[0]) // 1

if a, ok := arr[1].([]int); ok {
    fmt.Println(a[0])
} else if f, ok := arr[1].(func() string); ok {
    fmt.Println(f())  // func
}

或是使用 switch

arr := []interface{}{
    func() string { return "func" },
}

switch v := arr[0].(type){
case []int:
    fmt.Println(v[0])
case func() string:
    fmt.Println(v())
}

value.(type) 只能用在 switch

型別轉換

上面的情況是強制轉型,不過也有的情況是單純的型別轉換

type A interface {
	foo()
}

type B interface {
	foo()
}

type Duck struct {}

func (duck Duck) foo() {
	fmt.Println("Hello")
}

func main() {
	var some1 A = Duck{}
	var some2 B = some1

	some1.foo()
	some2.foo()
}

型別也能繼承,如:

type Parent interface {
	foo()
}

type Child interface {
	Parent
	bar()
}

type Duck struct {}

func (duck Duck) foo() {
	fmt.Println("Hello Foo")
}

func (duck Duck) bar() {
	fmt.Println("Hello Bar")
}

func main() {
	var some1 Child = Duck{}
	var some2 Parent = some1
	// Parent 不能直接轉回 Child
	// var some3 Child = some2
	var some3 Child = some2.(Duck)
	var some4 Child = some2.(Child)

	some1.bar()
	some2.foo()
	some3.bar()
	some4.bar()
}

中間會發現, Parent 不能轉回 Child 了,除非再強制轉型 Duck 或是 Child

因為這是強型態的特色,但只要轉換過去有達到介面實作的條件就能正常轉換了;而實作的條件最一開始就有提了:它不會管是不是繼承或是結構,而是只要實體有實作介面的行為就可以了。

參考資料


上一篇
Add Command Parameters
下一篇
The End
系列文
從無到有,使用 Go 開發應用程式30

尚未有邦友留言

立即登入留言