簡單地說 Interface是一組Method的組合,可以通過Interface來定義對象的一組行為。
如果某個對象實現了某個接口的所有方法,就表示它實現了該借口,無需顯式地在該類型上添加接口說明。
Interface是一個方法的集合,它里面沒有其他類型變量,而且Method只用定義原型 不用實現
①接口定義
1.命名時習慣以"er"結尾,如Printer Reader Writer
2.一個Interface的Method不宜過多,一般0~3個
3.一個Interface可以被任意的對象事項;相應地,一個對象也可以實現多個Interface
示例:
type People struct{ Name string } type Student struct{ People School string } type Teacher struct{ People Department string } func (p People) SayHi(){} func (s Student) SayHi(){} func (t Teacher) SayHi(){} func (s Student) Study(){} //根據struct的方法提取接口 從而使struct自動實現了該接口 type Speaker interface{ SayHi() } type Learner interface{ SayHi() Study() }
上面的例子中,Speaker接口被對象People,Teacher,Student實現;而Student同時實現了接口Speaker和Learner。
接口組合:
type SpeakLearner interface { Speaker Learner }//組合后使得SpeakLearner具有Speaker和Learner的功能
空接口:
任何類型都實現了空接口,相當於Java中的Object類
func test(a interface{}){}//該方法可以接受任意類型(int rune float32 struct...)的參數
②接口執行機制和接口賦值
首先介紹一種Go語言帶接收者(Receiver)的函數機制(下面的兩種情況執行結果一樣,涉及到struct成員值改變時仍然一樣)
情況1:
package main import ( "fmt" ) type People struct { Name string } func (p People) SayHi(){ //此處的Receiver是strcut fmt.Println("hello, this is", p.Name) } func (p *People) Study(){//此處的Receiver是****struct fmt.Printf("%s is studying\n", p.Name) } type SpeakLearner interface { SayHi() Study() } func main() { people := People{"zhangsan"}//這里的people為People類型 people.SayHi() people.Study() }
情況2:
func main() { people := &People{"zhangsan"}//這里的people為**People類型,即指針 people.SayHi() people.Study() }
通過上面的例子可以看出Receiver為People和*People的函數均可被People或者*People兩種類型調用,接下來借可能有在調用過程中People與*People之間的轉換問題
看下面的例子:
package main import ( "fmt" ) type Example struct{ Integer1 int Integer2 int } func (e Example) Assign(num1 int, num2 int) { e.Integer1, e.Integer2 = num1, num2 } func (e *Example) Add(num1 int, num2 int) { e.Integer1 +=num1 e.Integer2 +=num2 } func main(){ var e1 Example = Example{3,4} e1.Assign(1,1) fmt.Println(e1) e1.Add(1,1) fmt.Println(e1) var e2 *Example = &Example{3,4} e2.Assign(1,1) fmt.Println(e2) e2.Add(1,1) fmt.Println(e2) }
以上程序的執行結果為:
{3,4} {4,5} &{3,4} &{4,5}
可以看出實際執行的過程按函數定義前的Receiver類型執行。
對於接口的執行機制:
1.T僅擁有屬於T類型的方法集,而*T則同時擁有(T+*T)方法集
2.基於T實現方法,表示同時實現了interface和interface(*T)接口
3.基於*T實現方法,那就只能是對interface(*T)實現接口
type Integer int func (a Integer) Less(b Integer) bool { return a < b } func (a *Integer) Add(b Integer) { *a += b } 相應地,我們定義接口LessAdder,如下: type LessAdder interface { Less(b Integer) bool Add(b Integer) } 現在有個問題:假設我們定義一個Integer類型的對象實例,怎么將其賦值給LessAdder接口呢? 應該用下面的語句(1),還是語句(2)呢? var a Integer = 1 var b LessAdder = &a ... (1) var b LessAdder = a ... (2) 答案是應該用語句(1)。原因在於,Go語言可以根據下面的函數: func (a Integer) Less(b Integer) bool 即自動生成一個新的Less()方法: func (a *Integer) Less(b Integer) bool { return (*a).Less(b) } 這樣,類型*Integer就既存在Less()方法,也存在Add()方法,滿足LessAdder接口。 而從另一方面來說,根據 func (a *Integer) Add(b Integer) 這個函數無法自動生成以下這個成員方法: func (a Integer) Add(b Integer) { (&a).Add(b) } 因為(&a).Add()改變的只是函數參數a,對外部實際要操作的對象並無影響,這不符合用 戶的預期。所以,Go語言不會自動為其生成該函數。 因此,類型Integer只存在Less()方法,缺少Add()方法,不滿足LessAdder接口,故此上面的語句(2)不能賦值。
接口賦值舉例:
package main import( "fmt" ) //定義對象People、Teacher和Student type People struct { Name string } type Teacher struct{ People Department string } type Student struct{ People School string } //對象方法實現 func (p People) SayHi() { fmt.Printf("Hi, I'm %s. Nice to meet you!\n",p.Name) } func (t Teacher) SayHi(){ fmt.Printf("Hi, my name is %s. I'm working in %s .\n", t.Name, t.Department) } func (s Student) SayHi() { fmt.Printf("Hi, my name is %s. I'm studying in %s.\n", s.Name, s.School) } func (s Student) Study() { fmt.Printf("I'm learning Golang in %s.\n", s.School) } //定義接口Speaker和Learner type Speaker interface{ SayHi() } type Learner interface{ SayHi() Study() } func main() { people := People{"張三"} teacher := Teacher{People{"鄭智"}, "Computer Science"} student := Student{People{"李明"}, "Yale University"} var is Speaker //定義Speaker接口類型的變量 is = people //is能存儲People is.SayHi() is = teacher //is能存儲Teacher is.SayHi() is = student is.SayHi() //is能存儲Student var il Learner il = student //Learner類型接口的變量能存儲Student il.Study() }
執行結果為:
Hi, I'm 張三. Nice to meet you! Hi, my name is 鄭智. I'm working in Computer Science . Hi, my name is 李明. I'm studying in Yale University. I'm learning Golang in Yale University.
通過這個例子可以 看到(如同Java等語言)接口機制在多態和創建可擴展可重用的代碼時的重要作用
③匿名字段和接口轉換
若果接口類型S內部嵌入了接口類型T(匿名),則接口匿名字段方法集規則如下:
1.如果S嵌入匿名類型T,則S方法集包含T方法集。
2.如果S嵌入匿名類型*T,則S方法集包含*T方法集(包括Riceiver為T和*T的方法)。
3.如果S嵌入匿名類型T或*T,則*S方法集包含*T方法集(包括Riceiver為T和*T的方法)。(重要)
例如:
package main import( "fmt" ) type People struct { Name string } type S1 struct{ People //S1類型嵌入匿名People Department string } type S2 struct{ *People //S2類型嵌入匿名*People Department string } func (p People) Say1() { fmt.Printf("Hi, I'm %s. Say1111\n",p.Name) } func (p *People) Say2() { fmt.Printf("Hi, I'm %s. Say2222\n",p.Name) } type Speaker interface{ Say1() Say2() } func main() { people := People{"張三"} s1 := S1{People{"鄭智"}, "Computer Science"} s2 := S2{&People{"李明"}, "Math"} var is Speaker is = &people //*People實現了Speaker接口 is.Say1() is.Say2() //is = s1 //S1類型嵌入匿名People 不存在Say2()方法 因而未實現Speaker接口 //錯誤提示: cannot use s1 (type S1) as type Speaker in assignment: //S1 does not implement Speaker (Say2 method has pointer receiver) is = s2 //S2類型嵌入匿名*People 因而(p People) Say1()和(p *People) Say2()方法都有 實現了Speaker接口 is.Say1() is.Say2() is = &s1 //S1類型嵌入匿名People *S1 實現了Speaker接口 is.Say1() is.Say2() is = &s2 //S2類型嵌入匿名*People *S2 實現了Speaker接口 is.Say1() is.Say2() }
執行結果為:
Hi, I'm 張三. Say1111 Hi, I'm 張三. Say2222 Hi, I'm 李明. Say1111 Hi, I'm 李明. Say2222 Hi, I'm 鄭智. Say1111 Hi, I'm 鄭智. Say2222 Hi, I'm 李明. Say1111 Hi, I'm 李明. Say2222
從而證明了匿名字段方法集的3條規則。
接口轉換類似於說是接口繼承規則 可認為是實現復雜接口(方法多)向簡單接口(方法少)轉換,其中簡單接口中的方法在復雜接口中均有聲明 。例如:
package main import( "fmt" ) type People struct { Name string } type Student struct{ People School string } func (p People) GetPeopleInfo() { fmt.Println(p) } func (s Student) GetStudentInfo() { fmt.Println(s) } type PeopleInfo interface{ GetPeopleInfo() } type StudentInfo interface{ GetPeopleInfo() GetStudentInfo() } func main() { var is StudentInfo = Student{People{"李明"}, "Yele University"} is.GetStudentInfo() is.GetPeopleInfo() var ip PeopleInfo = is ip.GetPeopleInfo() ///ip.GetStudentInfo() note:ip.GetStudentInfo undefined }
④接口類型推斷:Comma-ok斷言和Switch測試
利用接口類型推斷可以 反向知道接口類型變量里面實際保存的是哪一種類型的對象。
Go語言中,常用兩種方法可以進行接口類型推斷,即Comma-ok斷言和Switch測試
Comma-ok斷言使用格式如下
value,ok = element.(T)
用法示例:
//利用Comma-ok斷言進行接口類型推斷 package main import( "fmt" ) type People struct{ Name string Age int } //定義空接口用於存儲任意類型數據類型 type Object interface{} func main() { people := People{"張三", 20} objs := make([]Object, 4) objs[0], objs[1], objs[2], objs[3] = 1, true, "Hello", people for index, element := range objs{ if value, ok := element.(int); ok{ fmt.Printf("objs[%d]類型是int,value=%d\n", index, value) }else if value, ok := element.(bool); ok{ fmt.Printf("objs[%d]類型是bool,value=%v\n", index, value) }else if value, ok := element.(string); ok{ fmt.Printf("objs[%d]類型是string,value=%s\n", index, value) }else if value, ok := element.(People); ok{ fmt.Printf("objs[%d]類型是Peole,value=%v\n", index, value) }else{ fmt.Printf("objs[%d]類型未知\n", index) } } }
結果是這樣的:
objs[0]類型是int,value=1 objs[1]類型是bool,value=true objs[2]類型是string,value=Hello objs[3]類型是Peole,value={張三 20}
使用Switch測試判斷接口類型,程序結構更加簡潔,示例如下(只修改了示例中的main函數):
func main() { people := People{"張三", 20} objs := make([]Object, 4) objs[0], objs[1], objs[2], objs[3] = 1, true, "Hello", people for index, element := range objs{ switch value := element.(type){ case int: fmt.Printf("objs[%d]類型是int,value=%d\n", index, value) case bool: fmt.Printf("objs[%d]類型是bool,value=%v\n", index, value) case string: fmt.Printf("objs[%d]類型是string,value=%s\n", index, value) case People: fmt.Printf("objs[%d]類型是Peole,value=%v\n", index, value) default: fmt.Printf("objs[%d]類型未知\n", index) } } }
執行結果Comma-ok方法相同,但是程序簡潔了許多。