手记

Go语言面向接口

Go语言开发(五)、Go语言面向接口

一、Duck Typing简介

1、Duck Typing简介

对于一门强类型的静态语言来说,要想通过运行时多态来隔离变化,多个实现类就必须属于同一类型体系,必须通过继承的方式与同一抽象类型建立is-a关系。
而Duck Typing则是一种基于特征,而不是基于类型的多态方式。Duck Typing仍然关心is-a,只不过is-a关系是以对方是否具备相关的特征来确定的。
是否满足is-a关系可以使用所谓的鸭子测试(Duck Test)进行判断。
"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。"

Duck Test是基于特征的哲学,给设计提供了强大的灵活性。动态面向对象语言,如Python,Ruby等都遵从了Duck Test来实现运行时多态。

2、C++对Duck Typing的支持

Duck Typing并不是动态语言的专利。C++作为一门强类型的静态语言,也对Duck Typing特性有强有力的支持。不过C++对Duck Typing特性支持不是在运行时,而是在编译时。
C++通过泛型编程实现对Duck Typing的支持。对于一个模板类或模板函数,会要求其实例化的类型必须具备某种特征,如某个函数签名、某个类型定义、某个成员变量等等。如果特征不具备,编译器会报错。
因此C++模板类、模板函数对要实例化的客户类提出了特征要求,客户类型需要实现相应的特征要求,从而复用模板的实现。
Duck Typing需要实例化的类型具备一致的特征,而模板特化的作用正是为了让不同类型具有统一的特征(统一的操作界面),所以模板特化可以作为Duck Typing与实例化类型之间的适配器。这种模板特化手段称为萃取(Traits),其中类型萃取最为常见。
类型萃取首先是一种非侵入性的中间层。否则,这些特征就必须被实例化类型提供,而就意味着,当一个实例化类型需要复用多个Duck Typing模板时,就需要迎合多种特征,从而让自己经常被修改,并逐渐变得庞大和难以理解。

一个Duck Typing模板,比如一个通用算法,需要实例化类型提供一些特征时,如果一个类型是类,则是一件很容易的事情,因为你可以在一个类里定义任何需要的特征。但如果一个基本类型也想复用此通用算法,由于基本类型无法靠自己提供算法所需要的特征,就必须借助于类型萃取。

3、Go语言对Duck Typing的支持

Go语言作为一种静态语言,对Duck Typing的支持通过Structural Typing实现。
Structural Typing是Go语言式的接口,就是不用显示声明类型T实现了接口I,只要类型T的公开方法完全满足接口I的要求,就可以把类型T的对象用在需要接口I的地方。

package mainimport "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()}

二、接口的定义和实现

1、接口的定义

Go语言的接口是一种数据类型,接口把所有的具有共性的方法定义在一起,任何其它类型只要实现了接口定义的方法就是实现了接口。接口的定义语法如下:

/* 定义接口 */type interface_name interface {   method_name1 [return_type]   method_name2 [return_type]   method_name3 [return_type]   ...   method_namen [return_type]}

2、接口的实现

接口的实现是隐式的,不需要显示声明实现了接口,只需要实现接口的方法接口。接口的实现语法如下:

/* 定义结构体 */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] {   /* 方法实现*/}

3、接口的定义与实现实例

package mainimport "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()}

三、接口的值类型

1、接口的值类型

一个类型可以实现任意数量的接口,每个类型都实现了一个空接口interface{}。接口是一组方法签名的集合,本质也是一种类型。
如果声明了一个接口变量,接口变量能够存储任何实现该接口的对象类型。
接口类型的本质就是如果一个数据类型实现了接口自身的方法集,那么该接口类型变量就能够引用该数据类型的值。
接口类型变量存储了两部分信息,一个是分配给接口变量的具体值,一个是值的类型的描述器,形式是(value, concrete type),而不是(value, interface type)。

2、空接口

空接口类型interface{}一个方法签名也不包含,所以所有的数据类型都实现了空接口。
空接口类型可以用于存储任意数据类型的实例。
如果一个函数的参数是空接口类型interface{},表明可以使用任何类型的数据。如果一个函数返回一个空接口类型,表明函数可以返回任何类型的数据。
interface{}可用于向函数传递任意类型的变量,但对于函数内部,该变量仍然为interface{}类型(空接口类型),而不是传入的实参类型。
利用接口类型作为参数可以达到抽象数据类型的目的。
定义一个MaxInterface接口,包含三个方法签名:
Len() int:必须返回集合数据结构的长度
Get(int i) interface{}:必须返回一个在索引i的数据元素
Bigger(i, j int) bool: 返回位于索引i和j的数值比较结果
满足MaxInterface接口的数据类型需要实现以上三个方法。

package mainimport "fmt"//Person类型type Person struct{   name string   age int}//切片类型type IntSlice []inttype FloatSlice []float32type 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)}

3、类型断言

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 hascase bool:   fmt.Printf("boolean %t\n", t)             // t has type boolcase int:   fmt.Printf("integer %d\n", t)             // t has type intcase *bool:   fmt.Printf("pointer to boolean %t\n", *t) // t has type *boolcase *int:   fmt.Printf("pointer to integer %d\n", *t) // t has type *int}

四、反射机制

Go语言实现了反射,所谓反射就是能检查程序在运行时的状态。
reflect包实现了运行时反射,允许程序操作任意类型的对象。

1、获取Value、Type对象

将变量转化成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信息。

2、获取对象或者变量的类型

Value.Type()和Value.Kind()方法都可以获取对象或者变量的类型,如果是变量的话,获取到的类型都相同;如果是结构体对象,Value.Type()返回结构体的名称,Value.Kind()返回“struct”;如果是自定义类型,Value.Type()返回自定义类型名称,Value.Kind()返回自定义类型的底层存储类型。因此,Value.Kind()可以用于判断变量是否是结构体。
Kind()描述的是reflection对象的底层类型,而不是静态类型。假如一个reflection对像包含了一个用户自定义的静态类型,Kind()方法返回的是底层数据类型,而不是自定义静态类型。

package mainimport (   "reflect"   "fmt")type Float float64type 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}

3、获取变量的值和给变量赋值

获取变量的值使用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 mainimport (   "reflect"   "fmt")type Float float64type 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改变值,私有的变量是无法改变值的。

4、获取结构体成员变量的tag信息

由于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

五、接口的组合

1、结构体嵌入类型

结构体类型可以包含匿名或者嵌入字段。当嵌入一个类型到结构体中时,嵌入类型的名字充当了嵌入字段的字段名。

package mainimport "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方法,本质上是内部类型的方法。

2、Go语言的方法提升

Go语言中内部类型方法集提升的规则如下:
A、如果S包含一个匿名字段T,S和S的方法集都包含接收者为T的方法提升。
当嵌入一个类型,嵌入类型的接收者为值类型的方法将被提升,可以被外部类型的值和指针调用。
B、对于
S类型的方法集包含接收者为T的方法提升
当嵌入一个类型,可以被外部类型的指针调用的方法集只有嵌入类型的接收者为指针类型的方法集,即当外部类型使用指针调用内部类型的方法时,只有接收者为指针类型的内部类型方法集将被提升。
C、如果S包含一个匿名字段
T,S和S的方法集都包含接收者为T或者T 的方法提升
当嵌入一个类型的指针,嵌入类型的接收者为值类型或指针类型的方法将被提升,可以被外部类型的值或者指针调用。
D、如果S包含一个匿名字段T,S的方法集不包含接收者为*T的方法提升。
根据Go语言规范里方法提升中的三条规则推导出的规则。当嵌入一个类型,嵌入类型的接收者为指针的方法将不能被外部类型的值访问。

3、接口的组合

GO语言中可以通过接口的组合,创建新的接口,新的接口默认继承组合的接口的抽象方法。

package mainimport "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)}

0人推荐
随时随地看视频
慕课网APP