接口與反射
接口是什么
Go 語言不是一種 “傳統” 的面向對象編程語言:它里面沒有類和繼承的概念。
但是 Go 語言里有非常靈活的 接口 概念,通過它可以實現很多面向對象的特性。接口提供了一種方式來 說明 對象的行為:如果誰能搞定這件事,它就可以用在這兒。
接口定義了一組方法(方法集),但是這些方法不包含(實現)代碼:它們沒有被實現(它們是抽象的)。接口里也不能包含變量。
通過如下格式定義接口:
type Namer interface {Method1(param_list) return_typeMethod2(param_list) return_type...
}
不像大多數面向對象編程語言,在 Go 語言中接口可以有值,一個接口類型的變量或一個 接口值 :var ai Namer,ai 是一個多字(multiword)數據結構,它的值是 nil。它本質上是一個指針,雖然不完全是一回事。指向接口值的指針是非法的,它們不僅一點用也沒有,還會導致代碼錯誤。
類型(比如結構體)實現接口方法集中的方法,每一個方法的實現說明了此方法是如何作用于該類型的:即實現接口,同時方法集也構成了該類型的接口。實現了 Namer 接口類型的變量可以賦值給 ai (接收者值),此時方法表中的指針會指向被實現的接口方法。當然如果另一個類型(也實現了該接口)的變量被賦值給 ai,這二者(譯者注:指針和方法實現)也會隨之改變
-
類型不需要顯式聲明它實現了某個接口:接口被隱式地實現。多個類型可以實現同一個接口。
-
實現某個接口的類型(除了實現接口方法外)可以有其他的方法。
-
一個類型可以實現多個接口。
-
接口類型可以包含一個實例的引用, 該實例的類型實現了此接口(接口是動態類型)。
?
package main
?
import "fmt"
?
type Sharper interface {Area() float32
}
?
type Square struct {side float32
}
?
func (sq *Square) Area() float32 {return sq.side * sq.side
}
?
func main() {sq := new(Square)sq.side = 6
?var area Sharperarea = sqfmt.Printf("the result is %f", area.Area())
}?
接口嵌套接口
type ReadWrite interface {Read(b Buffer) boolWrite(b Buffer) bool
}
?
type Lock interface {Lock()Unlock()
}
?
type File interface {ReadWriteLockClose()
}
一個接口類型的變量 varI 中可以包含任何類型的值,必須有一種方式來檢測它的 動態 類型,即運行時在變量中存儲的值的實際類型。在執行過程中動態類型可能會有所不同,但是它總是可以分配給接口變量本身的類型。通常我們可以使用 類型斷言 來測試在某個時刻 varI 是否包含類型 T 的值:
v := varI.(T) ? ? ? // unchecked type assertion
更安全的方式是使用以下形式來進行類型斷言:
if v, ok := varI.(T); ok { ?// checked type assertionProcess(v)return
}
// varI is not of type T
類型判斷:type-switch
switch t := areaIntf.(type) {
case *Square:fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:fmt.Printf("nil value: nothing to check?\n")
default:fmt.Printf("Unexpected type %T\n", t)
}
測試一個值是否實現了某個接口
type Stringer interface {String() string
}
?
if sv, ok := v.(Stringer); ok {fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}
空接口
概念
空接口或者最小接口 不包含任何方法,它對實現不做任何要求:
type Any interface {}
任何其他類型都實現了空接口(它不僅僅像 Java/C# 中 Object 引用類型),any 或 Any 是空接口一個很好的別名或縮寫。
空接口類似 Java/C# 中所有類的基類: Object 類,二者的目標也很相近。
可以給一個空接口類型的變量 var val interface {}
賦任何類型的值。
package main
?
import "fmt"
?
var i = 5
var str = "ABC"
?
type Person struct {name stringage ?int
}
?
type Any interface{}
?
func main() {var val Anyval = 5fmt.Printf("val has the value: %v\n", val)val = strfmt.Printf("val has the value: %v\n", val)pers1 := new(Person)pers1.name = "Rob Pike"pers1.age = 55val = pers1fmt.Printf("val has the value: %v\n", val)switch t := val.(type) {case int:fmt.Printf("Type int %T\n", t)case string:fmt.Printf("Type string %T\n", t)case bool:fmt.Printf("Type boolean %T\n", t)case *Person:fmt.Printf("Type pointer to Person %T\n", t)default:fmt.Printf("Unexpected type %T", t)}
}
反射包
方法和類型的反射
變量的最基本信息就是類型和值:反射包的 Type
用來表示一個 Go 類型,反射包的 Value
為 Go 值提供了反射接口。
兩個簡單的函數,reflect.TypeOf 和 reflect.ValueOf,返回被檢查對象的類型和值。例如,x 被定義為:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64,reflect.ValueOf(x) 返回 <float64 Value>
通過反射修改 (設置) 值
package main
?
import ("fmt""reflect"
)
?
func main() {var x float64 = 3.4v := reflect.ValueOf(x)// setting a value:// v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable valuefmt.Println("settability of v:", v.CanSet())v = reflect.ValueOf(&x) // Note: take the address of x.fmt.Println("type of v:", v.Type())fmt.Println("settability of v:", v.CanSet())v = v.Elem()fmt.Println("The Elem of v is: ", v)fmt.Println("settability of v:", v.CanSet())v.SetFloat(3.1415) // this works!fmt.Println(v.Interface())fmt.Println(v)
}
反射結構體
package main
?
import ("fmt""reflect"
)
?
type NotknownType struct {s1, s2, s3 string
}
?
func (n NotknownType) String() string {return n.s1 + " - " + n.s2 + " - " + n.s3
}
?
// variable to investigate:
var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}
?
func main() {value := reflect.ValueOf(secret) // <main.NotknownType Value>typ := reflect.TypeOf(secret) ? ?// main.NotknownType// alternative://typ := value.Type() // main.NotknownTypefmt.Println(typ)knd := value.Kind() // structfmt.Println(knd)
?// iterate through the fields of the struct:for i := 0; i < value.NumField(); i++ {fmt.Printf("Field %d: %v\n", i, value.Field(i))// error: panic: reflect.Value.SetString using value obtained using unexported field//value.Field(i).SetString("C#")}
?// call the first method, which is String():results := value.Method(0).Call(nil)fmt.Println(results) // [Ada - Go - Oberon]
}
接口與動態類型
Go 的動態類型
Go 沒有類:數據(結構體或更一般的類型)和方法是一種松耦合的正交關系。
Go 中的接口跟 Java/C# 類似:都是必須提供一個指定方法集的實現。但是更加靈活通用:任何提供了接口方法實現代碼的類型都隱式地實現了該接口,而不用顯式地聲明。
動態方法調用
Go 的實現通常需要編譯器靜態檢查的支持:當變量被賦值給一個接口類型的變量時,編譯器會檢查其是否實現了該接口的所有函數。如果方法調用作用于像 interface{} 這樣的 “泛型” 上,你可以通過類型斷言來檢查變量是否實現了相應接口。
顯式地指明類型實現了某個接口
如果你希望滿足某個接口的類型顯式地聲明它們實現了這個接口,你可以向接口的方法集中添加一個具有描述性名字的方法。例如:
type Fooer interface {Foo()ImplementsFooer()
}
空接口和函數重載
在 Go 語言中函數重載可以用可變參數 ...T 作為函數最后一個參數來實現。如果我們把 T 換為空接口,那么可以知道任何類型的變量都是滿足 T (空接口)類型的,這樣就允許我們傳遞任何數量任何類型的參數給函數,即重載的實際含義。
接口的繼承
當一個類型包含(內嵌)另一個類型(實現了一個或多個接口)的指針時,這個類型就可以使用(另一個類型)所有的接口方法。
類型可以通過繼承多個接口來提供像 多重繼承
一樣的特性
Go 中的面向對象
Go 沒有類,而是松耦合的類型、方法對接口的實現。
OO 語言最重要的三個方面分別是:封裝,繼承和多態,在 Go 中它們實現如下:
-
封裝(數據隱藏):和別的 OO 語言有 4 個或更多的訪問層次相比,Go 把它簡化為了 2 層(導出和不可導出)
-
包范圍內的:通過標識符首字母小寫,
對象
只在它所在的包內可見 -
可導出的:通過標識符首字母大寫,
對象
對所在包以外也可見
-
-
繼承:用組合實現:內嵌一個(或多個)包含想要的行為(字段和方法)的類型;多重繼承可以通過內嵌多個類型實現
-
多態:用接口實現:某個類型的實例可以賦給它所實現的任意接口類型的變量。類型和接口是松耦合的,并且多重繼承可以通過實現多個接口實現。Go 接口不是 Java 和 C# 接口的變體,而且:接口間是不相關的,并且是大規模編程和可適應的演進型設計的關鍵。