介面(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]) fmt.Println(arr[0][0])
|
它說 interface{}
不支援索引取值。
其實這是強型態的特色,宣告 interface{}
,它就把這個變數當成是 interface{}
,因此它不能當作 slice 操作。除非要拜託它把變數當成 slice:
arr := []interface{}{ []int{1, 2, 3}, }
fmt.Println(arr[0]) fmt.Println(arr[0].([]int)[0])
|
var.(type)
這是轉型的操作。如果可行的話,它會轉型成 type
,然後後面就可以繼續接 type
能做的操作。
它跟 Map 一樣會回傳第兩個值是 ok:
arr := []interface{}{ []int{1, 2, 3}, func() string { return "func" }, }
fmt.Println(arr[0].([]int)[0]) fmt.Println(arr[1].([]int)[0])
|
因此可以這樣處理
arr := []interface{}{ []int{1, 2, 3}, func() string { return "func" }, }
fmt.Println(arr[0].([]int)[0])
if a, ok := arr[1].([]int); ok { fmt.Println(a[0]) } else if f, ok := arr[1].(func() string); ok { fmt.Println(f()) }
|
或是使用 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 var some3 Child = some2.(Duck) var some4 Child = some2.(Child)
some1.bar() some2.foo() some3.bar() some4.bar() }
|
中間會發現,Parent
不能轉回 Child
了,除非再強制轉型 Duck
或是 Child
。
因為這是強型態的特色,但只要轉換過去有達到介面實作的條件就能正常轉換了;而實作的條件最一開始就有提了:它不會管是不是繼承或是結構,而是只要實體有實作介面的行為就可以了。
參考資料