您好,登錄后才能下訂單哦!
對于一門強類型的靜態語言來說,要想通過運行時多態來隔離變化,多個實現類就必須屬于同一類型體系,必須通過繼承的方式與同一抽象類型建立is-a關系。
而Duck Typing則是一種基于特征,而不是基于類型的多態方式。Duck Typing仍然關心is-a,只不過is-a關系是以對方是否具備相關的特征來確定的。
是否滿足is-a關系可以使用所謂的鴨子測試(Duck Test)進行判斷。
"當看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子。"
Duck Test是基于特征的哲學,給設計提供了強大的靈活性。動態面向對象語言,如Python,Ruby等都遵從了Duck Test來實現運行時多態。
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)
}
接口的實現是隱式的,不需要顯示聲明實現了接口,只需要實現接口的所有方法。接口的實現語法如下:
/* 定義結構體 */
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接口的數據類型需要實現以上三個方法。
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{}
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)
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。