當第一次看到這個名字的時候,腦中浮現的是個程式介面,但這個詞其實不是這樣。
介面 interface
其實是一種「方法的模板」,可以透過這個模板,去定義物件的一組行為。
先來看個例子:
package main
import "fmt"
type geometry interface{
area() float64
}
type rectangle struct {
width, height float64
}
func (r *rectangle) area() float64{
return r.width * r.height
}
func main(){
r1 := &rectangle{width:10, height:3}
fmt.Println(r1.area())
}
執行結果:
30
上面的例子,我們宣告了一個 geometry
的 interface
,在這個介面中必須實現 area()
這個 Method
。而這個 Method
的型態為 *rectengle
。
但此時將interface 全部註解,程式依舊可以正常運作,那介面到底是用來幹嘛的???
先來看一個例子:
package main
import "fmt"
type rectangle struct{
width, height float64
}
func (r *rectangle) area() float64{
return r.width * r.height
}
type circle struct{
radius float64
}
func (c *circle) area() float64{
return c.radius * c. radius * 3.1415
}
//==========================//
func (r *rectangle) printInfo(){
fmt.Printf("%#v\n", r)
fmt.Println("It's area:", r.area())
}
func (c *circle) printInfo() {
fmt.Printf("%#v\n", c)
fmt.Println("It's area:", c.area())
}
//==========================//
func main(){
r1 := &rectangle{width: 10, height: 3}
c1 := &circle{radius: 10}
r1.printInfo()
c1.printInfo()
}
執行結果:
&main.rectangle{width:10, height:3}
It's area: 30
&main.circle{radius:10}
It's area: 314.15000000000003
請看 18 行到 25 行,這兩個 Method 明明做的事基本一模一樣,但是因為型態不同,所以仍然需要兩個獨立的 Method 去實現。
如果我們把 *circle
及 *rectangle
都認定為「某個介面」,並以這個介面當做參數的型態會如何呢?
package main
import "fmt"
type geometry interface{
area() float64
}
type rectangle struct{
width, height float64
}
func (r *rectangle) area() float64{
return r.width * r.height
}
type circle struct{
radius float64
}
func (c *circle) area() float64{
return c.radius * c. radius * 3.1415
}
//==========================//
func printInfo(g geometry){
fmt.Printf("%#v\n", g)
fmt.Println("Its area:", g.area())
}
//==========================//
func main(){
r1 := &rectangle{width: 10, height: 3}
c1 := &circle{radius: 10}
r1.printInfo()
c1.printInfo()
}
執行結果:
&main.rectangle{width:10, height:3}
It's area: 30
&main.circle{radius:10}
It's area: 314.15000000000003
因為 *rectangle
和 *circle
都滿足 geometry
所提出的條件 area() float64
,所以可以將 *rectangle
和 *circle
視為 geometry
這個型態。
前面一章最後一個例子中 Student
和 Employee
都能 SayHi
,雖然他們的內部實現不一樣,但是那不重要,重要的是他們都能 say hi
讓我們來繼續做更多的擴充套件,Student
和 Employee
實現另一個方法 Sing
,然後 Student
實現方法 BorrowMoney
而 Employee
實現 SpendSalary
。
這樣 Student
實現了三個方法:SayHi
、Sing
、BorrowMoney
;而 Employee
實現了 SayHi
、Sing
、SpendSalary
。
上面這些方法的組合稱為 interface
(被物件 Student
和 Employee
實現)。例如 Student
和 Employee
都實現了 interface
:SayHi
和 Sing
,也就是這兩個物件是該 interface
型別。而 Employee
沒有實現這個 interface
:SayHi
、Sing
和 BorrowMoney
,因為 Employee
沒有實現 BorrowMoney
這個方法。
interface 型別定義了一組方法,如果某個物件實現了某個介面的所有方法,則此物件就實現了此介面。
package main
import "fmt"
// 定義 type
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名欄位 Human
school string
loan float32
}
type Employee struct {
Human //匿名欄位 Human
company string
money float32
}
// 定義 interface
type Men interface {
SayHi()
Sing(lyrics string)
Guzzle(beerStein string)
}
type YoungChap interface {
SayHi()
Sing(song string)
BorrowMoney(amount float32)
}
type ElderlyGent interface {
SayHi()
Sing(song string)
SpendSalary(amount float32)
}
//Human 物件實現 Sayhi 方法
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
// Human 物件實現 Sing 方法
func (h *Human) Sing(lyrics string) {
fmt.Println("La la, la la la, la la la la la...", lyrics)
}
//Human 物件實現 Guzzle 方法
func (h *Human) Guzzle(beerStein string) {
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}
// Employee 過載 Human 的 Sayhi 方法
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //此句可以分成多行
}
//Student 實現 BorrowMoney 方法
func (s *Student) BorrowMoney(amount float32) {
s.loan += amount // (again and again and...)
}
//Employee 實現 SpendSalary 方法
func (e *Employee) SpendSalary(amount float32) {
e.money -= amount // More vodka please!!! Get me through the day!
}
透過上面的程式碼可以知道,interface
可以被任意的物件實現。上面 Men
的 interface
被 Human
, Student
和 Employee
實現。同理,一個物件可以實現任意多個 interface
,例如:上面的 Student
實現了 Men
和 YoungChap
兩個 interface
。
最後,任意的型別都實現了 interface (我們這樣定義:interface{}),也就是包含 0 個 method
的 interface
。
那麼 interface
裡面到底能存什麼值呢?如果我們定義了一個 interface
的變數,那麼這個變數裡面可以存實現這個 interface
的任意型別的物件。例如上面例子中,我們定義了一個 Men
interface
型別的變數 m
,那麼 m
裡面可以存 Human
、Student
或者 Employee
值。
因為 m
能夠持有這三種類型的物件,所以我們可以定義一個包含 Men
型別元素的 slice
,這個 slice
可以被賦予實現了 Men
介面的任意結構的物件,這個和我們傳統意義上面的 slice
有所不同
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名欄位
school string
loan float32
}
type Employee struct {
Human //匿名欄位
company string
money float32
}
// 來做點 Method
// Human 實現 SayHi 方法
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
// Human 實現 Sing 方法
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
}
// Employee 過載 Human 的 SayHi 方法
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone)
}
// Interface
// Interface Men 被 Human,Student 和 Employee 實現
// 因為這三個型別都實現了這兩個方法
type Men interface {
SayHi()
Sing(lyrics string)
}
func main() {
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
// 定義 Men 型別的變數 i
var i Men
// i 能儲存 Student
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("November rain")
// i 也能儲存 Employee
i = tom
fmt.Println("This is tom, an Employee:")
i.SayHi()
i.Sing("Born to be wild")
// 定義 slice Men
fmt.Println("Let's use a slice of Men and see what happens")
x := make([]Men, 3)
// 這三個都是不同型別的元素,但是他們實現了 interface 同一個介面
x[0], x[1], x[2] = paul, sam, mike
for _, value := range x{
value.SayHi()
}
}
透過上面的程式碼,你會發現 interface
就是一組抽象方法的集合,它必須由其他非 interface
型別實現,而不能自我實現,Go 透過 interface
實現了 duck-typing
:即"當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子
空 interface
(interface{}
) 不包含任何的 Method
,正因為如此,所有的型別都實現了空 interface
。空 interface
對於描述起不到任何的作用(因為它不包含任何的 Method
),但是空 interface
在我們需要儲存任意型別的數值的時候相當有用,因為它可以儲存任意型別的數值。
// 先定義 a 空介面
var a interface{}
var i int = 5
s := "Hello World!!"
// a 可以儲存任意型別的數值
a = i
a = s
一個函式把 interface{}
作為參數,那麼他可以接受任意型別的值作為參數,如果一個函式回傳 interface{}
,那麼也就可以回傳任意型別的值。
interface 的變數可以持有任意實現該 interface 型別的物件,這給我們編寫函式(包括 method)提供了一些額外的思考,我們是不是可以透過定義 interface 參數,讓函式接受各種型別的參數。
舉個例子,fmt.Println
是常用的一個函式,但是是否注意到它可以接受任意型別的資料。
開啟 fmt
的原始碼檔案,你會看到這樣一個定義:
type Stringer interface {
String() string
}
也就是說,任何實現了 String 方法的型別作為參數被 fmt.Println 呼叫,例如:
package main
import (
"fmt"
"strconv"
)
type Human struct {
name string
age int
phone string
}
func (h human) String() string {
return "❰" + h.name + "-" + strconv.Itoa(h.age) + " years - ✆ " + h.phone + "❱"
}
func main() {
Bob := Human{"Bob", 39, "000-7777-XXX"}
fmt.Println("This Human is : ", Bob)
}
現在我們再回顧一下前面的 Box
範例,你會發現 Color
結構也定義了一個 method
:String
。其實這也是實現了 fmt.Stringer
這個 interface
,即如果需要某個型別能被 fmt
套件以特殊的格式輸出,你就必須實現 Stringer
這個介面。如果沒有實現這個介面,fmt
將以預設的方式輸出。
//實現同樣的功能
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())
注:實現了 error 介面的物件(即實現了 Error() string 的物件),使用 fmt 輸出時,會呼叫 Error()方法,因此不必再定義 String()方法了。
我們知道 interface 的變數裡面可以儲存任意型別的數值(該型別實現了 interface)。那麼我們怎麼反向知道這個變數裡面實際儲存了的是哪個型別的物件呢?目前常用的有兩種方法:
switch 測試
最好的講解就是程式碼例子,現在讓我們重寫上面的這個實現。
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//列印
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 //an int
list[1] = "Hello" //a string
list[2] = Person{"Dennis", 70}
for index, element := range list{
switch value := element.(type) {
case int:
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
case string:
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
case Person:
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
default:
fmt.Println("list[%d] is of a different type", index)
}
}
}
Go 語言裡面有一個語法,可以直接判斷是否是該型別的變數: value, ok = element.(T),這裡 value 就是變數的值,ok 是一個 bool 型別,element 是 interface 變數,T 是斷言的型別。
如果 element 裡面確實儲存了 T 型別的數值,那麼 ok 回傳 true,否則回傳 false。
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//定義了 String 方法,實現了 fmt.Stringer
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"Dennis", 70}
for index, element := range list {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
} else {
fmt.Printf("list[%d] is of a different type\n", index)
}
}
}
是不是很簡單!你是否注意到了多個 if 裡面,還記得我前面介紹流程時講過,if 裡面允許初始化變數。
也許你注意到了,我們斷言的型別越多,那麼 if else 也就越多,所以才引出了下面要介紹的 switch。
最好的講解就是程式碼例子,現在讓我們重寫上面的這個實現。
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//列印
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 //an int
list[1] = "Hello" //a string
list[2] = Person{"Dennis", 70}
for index, element := range list{
switch value := element.(type) {
case int:
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
case string:
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
case Person:
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
default:
fmt.Println("list[%d] is of a different type", index)
}
}
}
這裡有一點需要強調的是:element.(type)
語法不能在 switch 外的任何邏輯裡面使用,如果你要在 switch 外面判斷一個型別就使用comma-ok
。
Go 裡面真正吸引人的是它內建的邏輯語法,就像我們在學習 Struct 時學習的匿名欄位,多麼的優雅啊,那麼相同的邏輯引入到 interface 裡面,那不是更加完美了。如果一個 interface1 作為 interface2 的一個嵌入欄位,那麼 interface2 隱式的包含了 interface1 裡面的 method。
我們可以看到原始碼套件 container/heap 裡面有這樣的一個定義
type Interface interface {
sort.Interface //嵌入欄位 sort.Interface
Push(x interface{}) //a Push method to push elements into the heap
Pop() interface{} //a Pop elements that pops elements from the heap
}
我們看到 sort.Interface 其實就是嵌入欄位,把 sort.Interface 的所有 method 給隱式的包含進來了。也就是下面三個方法:
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less returns whether the element with index i should sort
// before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
另一個例子就是 io 套件下面的 io.ReadWriter ,它包含了 io 套件下面的 Reader 和 Writer 兩個 interface:
// io.ReadWriter
type ReadWriter interface {
Reader
Writer
}