Go基礎語法(七)
介面
介面一般這樣定義:介面定義一個物件的行為。
介面只指定了物件應該做什麼,至於如何實現這個行為(即實現細節),則由物件本身去確定。
在 Go 語言中,介面就是方法簽名(Method Signature)的集合。當一個型別定義了介面中的所有方法,我們稱它實現了該介面。這與面向物件程式設計(OOP)的說法很類似。介面指定了一個型別應該具有的方法,並由該型別決定如何實現這些方法。
示例程式碼:
package main import "fmt" //建立名為 VowelsFinder 的介面 type VowelsFinder interface { // 該介面有一個 FindVowels() []rune 的方法 FindVowels() []rune } //建立了一個 MyString 型別 type MyString string //MyString implements VowelsFinder func (ms MyString) FindVowels() []rune { var vowels []rune for _, rune := range ms { if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' { vowels = append(vowels, rune) } } return vowels } func main() { name := MyString("Sam Anderson") var v VowelsFinder v = name // possible since MyString implements VowelsFinder fmt.Printf("Vowels are %c", v.FindVowels()) }
上邊程式碼中,我們給接受者型別(Receiver Type)MyString 添加了方法 FindVowels() []rune
。此時,我們稱 MyString 實現了 VowelsFinder 介面。
這就和其他語言(如 Java)很不同,其他一些語言要求一個類使用 implement 關鍵字,來顯式地宣告該類實現了介面,而在 Go 中,並不需要這樣。如果一個型別包含了介面中宣告的所有方法,那麼它就隱式地實現了 Go 介面
。
程式碼中,建立一個型別為 VowelsFinder的變數 v ,name 的型別為 MyString,把 name 賦值給了 v。由於 MyString 實現了 VowelFinder,因此這是合法的
。在下一行,v.FindVowels() 呼叫了 MyString 型別的 FindVowels 方法
,列印字串 Sam Anderson 裡所有的母音。該程式輸出 Vowels are [a e o]。
祝賀!你已經建立並實現了你的第一個介面。
介面的用途
我們編寫一個簡單程式,根據公司員工的個人薪資,計算公司的總支出。
公司員工分兩種:
一種是長期員工,薪資分為基本工資和績效
一種是合同員工,薪資只有基本工資
上程式碼:
package main import ( "fmt" ) type SalaryCalculator interface { CalculateSalary() int } //長期員工: 基本工資、績效 type Permanent struct { empIdint basicpay int pfint } //合同員工:基本工資 type Contract struct { empIdint basicpay int } //長期員工的工資:基本工資和績效的總和 func (p Permanent) CalculateSalary() int { return p.basicpay + p.pf } //合同員工的工資:基本工資 func (c Contract) CalculateSalary() int { return c.basicpay } /* 該方法接收一個 SalaryCalculator 介面的切片([]SalaryCalculator)作為引數 通過呼叫不同型別對應的 CalculateSalary 方法,totalExpense 可以計算得到支出。 */ func totalExpense(s []SalaryCalculator) { expense := 0 for _, v := range s { expense = expense + v.CalculateSalary() } fmt.Printf("Total Expense Per Month $%d", expense) } func main() { pemp1 := Permanent{1, 5000, 20} pemp2 := Permanent{2, 6000, 30} cemp1 := Contract{3, 3000} employees := []SalaryCalculator{pemp1, pemp2, cemp1} totalExpense(employees) }
解釋程式碼:
- 聲明瞭一個 SalaryCalculator 介面型別,它只有一個方法 CalculateSalary() int。
-
定義的結構體:Permanent 和 Contract 長期員工和普通員工。
方法 CalculateSalary 分別實現了計算長期員工和普通員工的總工資。 - 由於 Permanent 和 Contract 都聲明瞭CalculateSalary方法,因此預設的它們都實現了 SalaryCalculator 介面。
- totalExpense 方法體現出了介面的妙用。該方法接收一個 SalaryCalculator 介面的切片([]SalaryCalculator)作為引數。我們向 totalExpense 方法傳遞了一個包含 Permanent 和 Contact 型別的切片。通過呼叫不同型別對應的 CalculateSalary 方法,totalExpense 可以計算得到支出。
- 這樣做最大的優點是:totalExpense 可以擴充套件新的員工型別,而不需要修改任何程式碼。假如公司增加了一種新的員工型別 Freelancer,它有著不同的薪資結構。Freelancer只需傳遞到 totalExpense 的切片引數中,無需 totalExpense 方法本身進行修改。只要 Freelancer 也實現了 SalaryCalculator 介面,totalExpense 就能夠實現其功能。
介面的內部表示
可以把介面看作內部的一個元組 (type, value)。 type 是介面底層的具體型別(Concrete Type),而 value 是具體型別的值。
package main import ( "fmt" ) type Test interface { Tester() } type MyFloat float64 func (m MyFloat) Tester() { fmt.Println(m) } func describe(t Test) { fmt.Printf("Interface type %T value %v\n", t, t) } func main() { var t Test f := MyFloat(89.7) t = f describe(t) t.Tester() }
程式碼中,describe 函式打印出了介面的具體型別和值。
空介面
不包含方法的介面稱為空介面。
空介面表示為 interface{}。
由於空介面沒有方法,因此所有型別都實現了空介面。
package main import ( "fmt" ) func describe(i interface{}) { fmt.Printf("Type = %T, value = %v\n", i, i) } func main() { s := "Hello World" describe(s) i := 55 describe(i) strt := struct { name string }{ name: "Naveen R", } describe(strt) }
上述程式碼:
分別給 describe 函式傳遞了 string、int 和 struct
型別斷言
型別斷言用於提取介面的底層值
在語法 i.(T) 中,介面 i 的具體型別是 T,該語法用於獲得介面的底層值。
package main import ( "fmt" ) func assert(i interface{}) { s := i.(int) //get the underlying int value from i fmt.Println(s) } func main() { var s interface{} = 56 assert(s) }
s 的具體型別是 int。在第 8 行,我們使用了語法 i.(int) 來提取 i 的底層 int 值。該程式會列印 56。
在上面程式中,如果具體型別不是 int,會發生什麼呢?
var s interface{} = "Steven Paul"
我們把具體型別為 string 的 s 傳遞給了 assert 函式,試圖從它提取出 int 值。該程式會報錯:panic: interface conversion: interface {} is string, not int.。
要解決該問題,我們可以使用以下語法:
v, ok := i.(T)
如果 i 的具體型別是 T,那麼 v 賦值為 i 的底層值,而 ok 賦值為 true。
如果 i 的具體型別不是 T,那麼 ok 賦值為 false,v 賦值為 T 型別的零值,此時程式不會報錯。
package main import ( "fmt" ) func assert(i interface{}) { v, ok := i.(int) fmt.Println(v, ok) } func main() { var s interface{} = 56 assert(s) var i interface{} = "Steven Paul" assert(i) }
型別選擇(Type Switch)
型別選擇的語法類似於型別斷言。型別斷言的語法是 i.(T),而對於型別選擇,型別 T 由關鍵字 type 代替
package main import ( "fmt" ) func findType(i interface{}) { switch i.(type) { case string: fmt.Printf("I am a string and my value is %s\n", i.(string)) case int: fmt.Printf("I am an int and my value is %d\n", i.(int)) default: fmt.Printf("Unknown type\n") } } func main() { findType("Naveen") findType(77) findType(89.98) }
switch i.(type) 表示一個型別選擇。每個 case 語句都把 i 的具體型別和一個指定型別進行了比較。如果 case 匹配成功,會打印出相應的語句
還可以將一個型別和介面相比較。如果一個型別實現了介面,那麼該型別與其實現的介面就可以互相比較。
package main import "fmt" type Describer interface { Describe() } type Person struct { name string ageint } func (p Person) Describe() { fmt.Printf("%s is %d years old", p.name, p.age) } func findType(i interface{}) { switch v := i.(type) { case Describer: v.Describe() default: fmt.Printf("unknown type\n") } } func main() { findType("Naveen") p := Person{ name: "Naveen R", age:25, } findType(p) }