對於一門強類型的靜態語言來講,要想經過運行時多態來隔離變化,多個實現類就必須屬於同一類型體系,必須經過繼承的方式與同一抽象類型創建is-a關係。
而Duck Typing則是一種基於特徵,而不是基於類型的多態方式。Duck Typing仍然關心is-a,只不過is-a關係是以對方是否具有相關的特徵來肯定的。
是否知足is-a關係可使用所謂的鴨子測試(Duck Test)進行判斷。
"當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就能夠被稱爲鴨子。"
Duck Test是基於特徵的哲學,給設計提供了強大的靈活性。動態面嚮對象語言,如Python,Ruby等都聽從了Duck Test來實現運行時多態。golang
Duck Typing並非動態語言的專利。C++做爲一門強類型的靜態語言,也對Duck Typing特性有強有力的支持。不過C++對Duck Typing特性支持不是在運行時,而是在編譯時。
C++經過泛型編程實現對Duck Typing的支持。對於一個模板類或模板函數,會要求其實例化的類型必須具有某種特徵,如某個函數簽名、某個類型定義、某個成員變量等等。若是特徵不具有,編譯器會報錯。
所以C++模板類、模板函數對要實例化的客戶類提出了特徵要求,客戶類型須要實現相應的特徵要求,從而複用模板的實現。
Duck Typing須要實例化的類型具有一致的特徵,而模板特化的做用正是爲了讓不一樣類型具備統一的特徵(統一的操做界面),因此模板特化能夠做爲Duck Typing與實例化類型之間的適配器。這種模板特化手段稱爲萃取(Traits),其中類型萃取最爲常見。
類型萃取首先是一種非侵入性的中間層。不然,這些特徵就必須被實例化類型提供,而就意味着,當一個實例化類型須要複用多個Duck Typing模板時,就須要迎合多種特徵,從而讓本身常常被修改,並逐漸變得龐大和難以理解。
一個Duck Typing模板,好比一個通用算法,須要實例化類型提供一些特徵時,若是一個類型是類,則是一件很容易的事情,由於你能夠在一個類裏定義任何須要的特徵。但若是一個基本類型也想複用此通用算法,因爲基本類型沒法靠本身提供算法所須要的特徵,就必須藉助於類型萃取。算法
Go語言做爲一種靜態語言,對Duck Typing的支持經過Structural Typing實現。
Structural Typing是Go語言式的接口,就是不用顯示聲明類型T實現了接口I,只要類型T的公開方法徹底知足接口I的要求,就能夠把類型T的對象用在須要接口I的地方。編程
package main import "fmt" type ISayHello interface { sayHello() } //美國人 type AmericalPerson struct {} func (person AmericalPerson)sayHello(){ fmt.Println("Hello!") } //中國人 type ChinesePerson struct {} func (person ChinesePerson)sayHello(){ fmt.Println("你好!") } func greet(i ISayHello){ i.sayHello() } func main() { ameriacal := AmericalPerson{} chinese := ChinesePerson{} var i ISayHello i = ameriacal i.sayHello() i = chinese i.sayHello() }
Go語言的接口是一種抽象數據類型,是一系列接口的集合,接口把全部的具備共性的方法定義在一塊兒,任何其它類型只要實現了接口定義的方法就是實現了接口。接口是duck-type編程的一種體現,不關心屬性(數據),只關心行爲(方法)。
Go語言的接口由使用者定義。
接口的聲明語法以下:數組
/* 定義接口 */ type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] }
面向對象編程思想中事物的共同點構成了抽象的基礎,繼承關係解決了IS-A即定義問題,所以能夠把子類對象當作父類對象使用。但對於父類不一樣但又具備某些共同行爲的數據,繼承不能解決。單一繼承構造的是樹狀結構,而現實世界中常見的是網狀結構。
接口是在某一個方面的抽象,但不一樣於繼承,接口是鬆散的結構,不與定義綁定。Duck Typing相比繼承是更加鬆耦合的方式,能夠同時從多個維度對數據進行抽象,找出共同點,並使用同一套邏輯來處理。
面嚮對象語言如Java、C++的接口方式是先聲明後實現的強制模式,Go語言則不須要聲明接口,
實現之間應該少用繼承式的強關聯關係,多用接口式的弱關聯關係。接口已經能夠在不少方面替代繼承的做用,好比多態和泛型,並且接口的關係鬆散、隨意,能夠有更高的自由度、更多的抽象角度。數據結構
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } type Seeker interface { Seek(offset int64, whence int) (int64, error) }
接口的實現是隱式的,不須要顯示聲明實現了接口,只須要實現接口的全部方法。接口的實現語法以下:ide
/* 定義結構體 */ type struct_name struct { /* variables */ } /* 實現接口方法 */ func (struct_name_variable struct_name) method_name1() [return_type] { /* 方法實現 */ } ... func (struct_name_variable struct_name) method_namen() [return_type] { /* 方法實現*/ }
假設在另外一個地方中定義File類型:函數
type File struct { // ... } func (f *File) Read(buf []byte) (n int, err error) func (f *File) Write(buf []byte) (n int, err error) func (f *File) Seek(off int64, whence int) (pos int64, err error) func (f *File) Close() error File實現了4個接口,所以能夠將File對象賦值給任何一個接口。 var file1 Reader = new(File) var file2 Writer = new(File) var file3 Closer = new(File) var file4 Seeker = new(File)
package main import "fmt" //接口的定義 type ISayHello interface { sayHello() } //接口的實現 //美國人 type AmericalPerson struct {} func (person AmericalPerson)sayHello(){ fmt.Println("Hello!") } //接口的實現 //中國人 type ChinesePerson struct {} func (person ChinesePerson)sayHello(){ fmt.Println("你好!") } func greet(i ISayHello){ i.sayHello() } func main() { ameriacal := AmericalPerson{} chinese := ChinesePerson{} var i ISayHello i = ameriacal i.sayHello() i = chinese i.sayHello() }
一個類型能夠實現任意數量的接口,每一個類型都實現了一個空接口interface{}。
接口是一系列接口的集合,是一種抽象數據類型,接口變量能夠引用任何實現了接口的所有方法的具體數據類型的值。
接口變量存儲了兩部分信息,一個是分配給接口變量的具體值(接口實現者的值),一個是值的類型的描述器(接口實現者的類型),形式是(value, concrete type),而不是(value, interface type)。
實現接口的具體方法時,若是以指針做爲接收者,接口的具體實現類型只能以指針方式使用,值接收者既能夠按指針方式使用也能夠按值方式使用。測試
package main import "fmt" type Retriever interface { Get(url string) string } type MockRetriever struct { Contents string } // 值接收者 func (r MockRetriever) Get(url string) string { return r.Contents } type RealRetriever struct { Contents string } // 指針接收者 func (r *RealRetriever) Get(url string) string { return r.Contents } func main() { var retriever Retriever retriever = MockRetriever{"This is fake Retreiver"} fmt.Printf("%T %v\n", retriever, retriever) retriever = &MockRetriever{"This is fake Retreiver"} fmt.Printf("%T %v\n", retriever, retriever) retriever = &RealRetriever{"This is real Retriever"} //retriever = RealRetriever{"This is real Retriever"} //error fmt.Printf("%T %v\n", retriever, retriever) } // output: // main.MockRetriever {This is fake Retreiver} // *main.MockRetriever &{This is fake Retreiver} // *main.RealRetriever &{This is real Retriever}
空接口類型interface{}一個方法簽名也不包含,因此全部的數據類型都實現了空接口。
空接口類型能夠用於存儲任意數據類型的實例。
若是一個函數的參數是空接口類型interface{},代表可使用任何類型的數據。若是一個函數返回一個空接口類型,代表函數能夠返回任何類型的數據。
interface{}可用於向函數傳遞任意類型的變量,但對於函數內部,該變量仍然爲interface{}類型(空接口類型),而不是傳入的實參類型。
利用接口類型做爲參數能夠達到抽象數據類型的目的。
定義一個MaxInterface接口,包含三個方法簽名:
Len() int:必須返回集合數據結構的長度
Get(int i) interface{}:必須返回一個在索引i的數據元素
Bigger(i, j int) bool: 返回位於索引i和j的數值比較結果
知足MaxInterface接口的數據類型須要實現以上三個方法。ui
package main import "fmt" //Person類型 type Person struct{ name string age int } //切片類型 type IntSlice []int type FloatSlice []float32 type PersonSlice []Person //接口定義 type MaxInterface interface { Len() int Get(i int)interface{} Bigger(i,j int)bool } //Len()方法的實現 func (x IntSlice) Len()int{ return len(x) } func (x FloatSlice) Len()int{ return len(x) } func (x PersonSlice) Len()int{ return len(x) } //Get(i int)方法實現 func (x IntSlice) Get(i int)interface{}{ return x[i] } func (x FloatSlice) Get(i int)interface{}{ return x[i] } func (x PersonSlice) Get(i int)interface{}{ return x[i] } //Bigger(i,j int)方法實現 func (x IntSlice) Bigger(i,j int)bool{ if x[i] > x[j]{ return true }else{ return false } } func (x FloatSlice) Bigger(i,j int)bool{ if x[i] > x[j]{ return true }else { return false } } func (x PersonSlice) Bigger(i,j int)bool{ if x[i].age > x[j].age{ return true }else { return false } } //求最大值函數實現 func Max(data MaxInterface) (ok bool, max interface{}){ if data.Len() == 0{ return false,nil } if data.Len() == 1{ return true,data.Get(1) } max = data.Get(0) m := 0 for i:=1;i<data.Len();i++{ if data.Bigger(i,m){ max = data.Get(i) m = i } } return true, max } func main() { intslice := IntSlice{1, 2, 44, 6, 44, 222} floatslice := FloatSlice{1.99, 3.14, 24.8} group := PersonSlice{ Person{name:"Jack", age:24}, Person{name:"Bob", age:23}, Person{name:"Bauer", age:104}, Person{name:"Paul", age:44}, Person{name:"Sam", age:34}, Person{name:"Lice", age:54}, Person{name:"Karl", age:74}, Person{name:"Lee", age:4}, } _,m := Max(intslice) fmt.Println("The biggest integer in islice is :", m) _, m = Max(floatslice) fmt.Println("The biggest float in fslice is :", m) _, m = Max(group) fmt.Println("The oldest person in the group is:", m) }
[]T不能直接賦值給[]interface{}url
t := []int{1, 2, 3, 4} var s []interface{} = t
編譯時報錯:cannot use t (type []int) as type []interface {} in assignment
正確賦值方法:
t := []int{1, 2, 3, 4} s := make([]interface{}, len(t)) for i, v := range t { s[i] = v }
interface{}可用於向函數傳遞任意類型的變量,但對於函數內部,該變量仍然爲interface{}類型(空接口類型),而不是傳入的實參類型。
接口類型向普通類型的轉換稱爲類型斷言(運行期肯定)。
func printArray(arr interface{}){ //arr是空接口,不是數組類型,報錯 for _,v:=range arr{ fmt.Print(v," ") } fmt.Println() }
能夠經過類型斷言將接口類型轉換爲切片類型。
func printArray(arr interface{}){ //經過斷言實現類型轉換 a,_ := arr.([]int) for _,v:=range a{ fmt.Println(v, " ") } fmt.Println() }
在使用類型斷言時,最好判斷斷言是否成功。
b,ok := a.(T) if ok{ ... }
斷言失敗在編譯階段不會報錯,所以,若是不對斷言結果進行判斷將可能會斷言失敗致使運行錯誤。
不一樣類型變量的運算必須進行顯式的類型轉換,否者結果可能會溢出,致使出錯。
類型斷言也能夠配合switch語句進行判斷。
var t interface{} t = functionOfSomeType() switch t := t.(type) { default: fmt.Printf("unexpected type %T", t) // %T prints whatever type t has case bool: fmt.Printf("boolean %t\n", t) // t has type bool case int: fmt.Printf("integer %d\n", t) // t has type int case *bool: fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool case *int: fmt.Printf("pointer to integer %d\n", *t) // t has type *int }
能夠將一個實現接口的對象實例賦值給接口,也能夠將另一個接口賦值給接口。
將一個類對象實例賦值給一個接口前,要保證類實現了接口的全部方法。
package main import ( "fmt" "reflect" ) // 定義接口 type Animal interface { eat() drink() print() } type Bird struct { Name string } func (b *Bird) eat() { fmt.Println(b.Name,"eat") } func (b *Bird) drink() { fmt.Println(b.Name,"drink") } func (b *Bird) print() { fmt.Println(b.Name,"is Bird") } type People struct { Name string } func (p People) eat() { fmt.Println(p.Name,"eat") } func (p People) drink() { fmt.Println(p.Name,"drink") } func (p People) print() { fmt.Println(p.Name, "is People.") } func main() { var bob People = People{"BoB"} var people1 Animal = &bob people1.print() // BoB is People. fmt.Println(reflect.TypeOf(people1)) // *main.People var people2 Animal = bob people2.print() // BoB is People. fmt.Println(reflect.TypeOf(people2)) // main.People var pigeon Bird = Bird{"Jack."} var bird1 Animal = &pigeon bird1.print() // Jack is Bird. fmt.Println(reflect.TypeOf(bird1)) // *main.Bird //var bird2 Animal = pigeon // not ok //cannot use pigeon (type Bird) as type Animal in assignment: //Bird does not implement Animal (drink method has pointer receiver) }
var r io.Reader = new(os.File) var rw io.ReadWriter = r //not ok var rw2 io.ReadWriter = new(os.File) var r2 io.Reader = rw2 //ok
r沒有實現Write方法,因此不能賦值給rw。
Go語言實現了反射,所謂反射就是能檢查程序在運行時的狀態。
reflect包實現了運行時反射,容許程序操做任意類型的對象。
將變量轉化成reflect對象(reflect.Type或者reflect.Value)
t := reflect.TypeOf(i) //reflect.Type對象 v := reflect.ValueOf(i) //reflect.Value對象
調用reflect.TypeOf(x),x首先存儲在一個空接口上,而後在做爲參數傳遞給TypeOf函數; Reflect.TypeOf函數內部會解析空接口,接收類型信息。
同理,reflect.ValueOf函數內部會接收到一個value信息。
Value.Type()和Value.Kind()方法均可以獲取對象或者變量的類型,若是是變量的話,獲取到的類型都相同;若是是結構體對象,Value.Type()返回結構體的名稱,Value.Kind()返回「struct」;若是是自定義類型,Value.Type()返回自定義類型名稱,Value.Kind()返回自定義類型的底層存儲類型。所以,Value.Kind()能夠用於判斷變量是不是結構體。
Kind()描述的是reflection對象的底層類型,而不是靜態類型。假如一個reflection對像包含了一個用戶自定義的靜態類型,Kind()方法返回的是底層數據類型,而不是自定義靜態類型。
package main import ( "reflect" "fmt" ) type Float float64 type Person struct { name string age int } func main() { var x1 int = 8 value1 := reflect.ValueOf(x1) fmt.Println(value1.Type())//int fmt.Println(value1.Kind())//int var x Float = 3.14 value2 := reflect.ValueOf(x) fmt.Println(value2.Type())//Float fmt.Println(value2.Kind())//float64 person := Person{} value3 := reflect.ValueOf(person) fmt.Println(value3.Type())//Person fmt.Println(value3.Kind())//struct }
獲取變量的值使用value.Interface()方法,返回一個value的值,類型是interface。給變量賦值須要先判斷變量的類型,可使用Value.Kind()方法,若是變量的類型是reflect.Int,使用Value.SetInt()方法給變量賦值。
若是要修改reflection對象的值,reflection對象的值必須是可settable的。
Settability(可設置)是reflection Value的一個屬性, 並非全部的reflection Values都擁有Settability屬性。Settability屬性表示reflection對象是否能夠修改建立reflection對象的實際值,可設置取決於reflection對象所持有的原始值。
調用reflect.ValueOf(x)時,x做爲參數傳遞時,首先拷貝x,reflect.ValueOf函數中的interface值是x的拷貝,而不是x自己。若是想要經過reflection修改x, 必須傳遞一個x指針,獲取reflect.Value指針指向的對象,使用reflect.ValueOf(&x).Elem()。
package main import ( "reflect" "fmt" ) type Float float64 type Human struct { name string Age uint8 } func main() { var x1 int = 8 value1 := reflect.ValueOf(x1) fmt.Println(value1.Type())//int fmt.Println(value1.Kind())//int var x Float = 3.14 //獲取reflect.Value對象,屬性Settability爲false value2 := reflect.ValueOf(x) fmt.Println(value2.Type())//Float fmt.Println(value2.Kind())//float64 if value2.Kind() ==reflect.Float64{ if value2.CanSet(){ value2.SetFloat(3.1415926) } } fmt.Println(value2)//3.14 person := Human{"Bauer",30} //獲取reflect.Value指針指向的對象,屬性Settability爲true value3 := reflect.ValueOf(&person).Elem() fmt.Println(value3.Type())//Person fmt.Println(value3.Kind())//struct fmt.Println(value3)//{Bauer 30} field0 := value3.FieldByName("name") if field0.Kind() == reflect.String{ if field0.CanSet(){//私有成員不可設置 field0.SetString("Bob") } } fmt.Println(value3)//{Bauer 30} field1 := value3.FieldByName("Age") if field1.Kind() == reflect.Uint8{ if field1.CanSet(){//公有成員可設置 field1.SetUint(20) } } fmt.Println(value3)//{Bauer 20} }
對於結構體,只有公有的成員變量能夠被reflect改變值,私有的變量是沒法改變值的。
因爲golang變量大小寫和公有私有權限相關,開發者很難按照本身的意願來定義變量名,所以golang提供了tag機制,用於給變量提供一個標籤,標籤能夠做爲一個別名,來給一些存儲結構來獲取結構體變量名字使用。
type Person struct { name string `Country:"CN"` age uint8 } bob := Person{"Bob", 30} v := reflect.ValueOf(bob) vt := v.Type() filed,_ := vt.FieldByName("name") fmt.Println(filed.Tag.Get("Country"))//CN
結構體類型能夠包含匿名或者嵌入字段。當嵌入一個類型到結構體中時,嵌入類型的名字充當了嵌入字段的字段名。
package main import "fmt" type User struct { Name string EMail string } type Admin struct { User Level string } func (user *User)Notify() error{ fmt.Printf("User: Sending a Email to %s<%s>\n", user.Name,user.EMail) return nil } func main() { admin := &Admin{ User: User{ Name: "Bauer", EMail: "bauer@gmail.com", }, Level: "super", } admin.Notify() admin.User.Notify() }
當嵌入一個類型,嵌入類型的方法就變成了外部類型的方法,可是當嵌入類型的方法被調用時,方法的接受者是內部類型(嵌入類型),而非外部類型。
嵌入類型的名字充當着字段名,同時嵌入類型做爲內部類型存在,可使用如下方式的調用方法:admin.User.Notify()
上述代碼經過類型名稱來訪問內部類型的字段和方法。內部類型的字段和方法也一樣被提高到了外部類型,所以可使用如下方式調用方法:admin.Notify()
經過外部類型來調用Notify方法,本質上是內部類型的方法。
Go語言中內部類型方法集提高的規則以下:
A、若是S包含一個匿名字段T,S和S的方法集都包含接收者爲T的方法提高。
當嵌入一個類型,嵌入類型的接收者爲值類型的方法將被提高,能夠被外部類型的值和指針調用。
B、對於S類型的方法集包含接收者爲T的方法提高
當嵌入一個類型,能夠被外部類型的指針調用的方法集只有嵌入類型的接收者爲指針類型的方法集,即當外部類型使用指針調用內部類型的方法時,只有接收者爲指針類型的內部類型方法集將被提高。
C、若是S包含一個匿名字段T,S和S的方法集都包含接收者爲T或者T 的方法提高
當嵌入一個類型的指針,嵌入類型的接收者爲值類型或指針類型的方法將被提高,能夠被外部類型的值或者指針調用。
D、若是S包含一個匿名字段T,S的方法集不包含接收者爲*T的方法提高。
根據Go語言規範裏方法提高中的三條規則推導出的規則。當嵌入一個類型,嵌入類型的接收者爲指針的方法將不能被外部類型的值訪問。
GO語言中能夠經過接口的組合,建立新的接口,新的接口默認繼承組合的接口的抽象方法。
package main import "fmt" type IReader interface { Read(file string) []byte } type IWriter interface { Write(file string, data string) } // 接口組合,默認繼承了IReader和IWriter中的抽象方法 type IReadWriter interface { IReader IWriter } type ReadWriter struct { } func (rw *ReadWriter) Read(file string) []byte { fmt.Println(file) return nil } func (rw *ReadWriter) Write(file string, data string) { fmt.Printf("filename:%s, contents:%s",file,data) } func main() { readwriter := new(ReadWriter) var irw IReadWriter = readwriter // ok irw.Read("abc.txt") data := "hello world." irw.Write("abc.txt",data) }