接口定义
》接口中不能包含变量
》接口定义了一组方法,但是这些方法不包含实现代码
可以看到,后面是一个方法指针表,是通过运行时反射能力构建
的
》接口可以有值,一个接口类型的变量
// ai是多字数据结构,它的值是nil
// ai本质上是指针,但是又不完全是,但是定义指向ai的指针是非法的,这没有意义
var ai Namer
约定:
只包含一个方法的接口名字,由方法名加[e]r后缀组成,
像:Printer、Reader、Writer、Logger、Converter
不常用的方式,当后缀er不合适的时候,比如Recoverable或IRecover
加able后缀或者I前缀
接口实现
类型实现接口方法集中的方法,实现了接口类型的变量,可以赋值给接收者,此时方法表中的指针会指向被实现的接口方法,接口是被隐式实现,多个类型可以实现同一个接口,实现某个接口的类型可以有自己的方法
接口类型可以包含一个实例的引用,该实例的类型实现了接口,接口是动态类型
即使接口是在类型之后定义,二者处于不同的包中,被单独编译,只要类型实现了接口中的方法,就实现了此接口
接口内有几个方法,对应的实例就要实现几个方法,不然会报错,这个和PHP的接口特性一致
接口嵌套接口
一个接口可以嵌套其他的接口,这相当于将这些内嵌的接口方法列举在外层接口一样
类型断言:检测和转换接口变量的类型
一个接口类型的变量,里面可以包含任何类型的值,是一种动态类型
在执行过程中,动态类型会有所不同,但是接口总是可以分配给接口变量本身的类型
通过类型断言,可以测试在某个时刻,变量中是否包含类型T的值:
v := varI.(T)
varI必须是接口变量,不然会报错
接口断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能遇见所有的可能性,更安全的方式如下:
if v,ok := varI.(T);ok{ // ok就是转化结果,这样就不会有运行时错误发生
return
}
多数情况下,我们可能只是想测试ok的值,可以如下:
if _,ok := varI(T); ok {
return
}
注意:
这个 * 一定要加上,不然会导致编译错误,impossible type assertion: Square does not implement Shaper (Area method has pointer receiver)
type switch 断言类型
用途
处理来自外部的,类型未知的数据时,比如解析json或xml编码的数据时,类型测试和转换就会非常有用
测试一个值是否实现了某个接口
type Stringer interface {
String() string
}
if sv, ok := v.(Stringer); ok {
fmt.Printf(“v implements String(): %s\n”, sv.String()) // note: sv, not v
}
测试v是否实现了Stringer接口
使用方法集和接口
在接口上调用方法的时候,必须有和方法定义时相同的接收者类型或者是可以从具体类型p直接可以辨识的:
1.指针方法可以通过指针调用
2.值方法可以通过值调用
3.接收者是值的方法可以通过指针调用,因为指针会首先被解引用
4.接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址
将一个值赋值给一个接口时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。
Go 语言规范定义了接口方法集的调用规则:
类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
类型 T 的可调用方法集包含接受者为 T 的所有方法
类型 T 的可调用方法集不包含接受者为 *T 的方法
空接口
不包含任何方法,它对实现不做任何要求
任何其他类型都实现了接口,any或Any是空接口一个很好的别名和缩写
空接口类似java c#中所有类的基类
可以给一个空接口的变量赋任何类型的值
构建通用类型或包含不同类型变量的数组
定一个空接口别名的数组,里面可以放任何类型的数据
反射包
方法和类型的反射
反射是用程序检查其所拥有的结构,尤其是类型的一种能力,这是元编程的一种形式
可以在运行时检查类型和变量,例如它的大小和方法,动态的调用方法,对于没有源代码的包尤其有用
变量的基本信息就是类型和值,反射包的Type用来表示一个Go类型,反射包的Value为Go值提供了反射接口
反射是通过检查一个接口的值,变量首先被转换成空接口,例子如下:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
接口的值包含一个type和value
reflect.Type 和 reflect.Value 有许多方法用于检查和操作它们
其中,Value有一个Type方法返回reflect.Value的Type,另一个Type和Value都有一个Kind方法,返回一个常量来表示类型,Unit,Float64,Slice等等,同样Value有叫Int、FLoat的方法获取存储在内部的值
Kind()方法总是返回底层数据类型
Interface()方法可以得到还原值
通过反射修改值
此处会报错,原因是因为SetFloat在设置一个不可设置的值,是否可以设置是value的一个属性,可以用CanSet方法测试是否可设置
val是一个值拷贝,所以修改是不能修改原始值的,所以需要传递引用,但是还是不能修改
使用Elem方法,间接的使用了指针,这样才可以修改成功
反射结构
NumField() 返回结构内的字段数量,通过一个for循环用索引取得每个字段值Field(i)
调用的方式:调用签名在结构上的方法,Method(n).Call(nil)
只有被导出的字段,才是可以设置的
Printf 和 反射
在Go的标准库中,反射功能被大量地使用,举例:fmt包中的Printf以及其它格式化输出函数,都会使用反射来分析它的...
参数
接口与动态类型
与Ruby和Python比较,GO会在编译的时候检查参数是否实现了接口,而其它的只能报运行时错误
动态方法调用:
Ruby和Python中动态类型是延迟绑定的,方法只是用参数和变量简单地调用,然后在运行时才解析,但是这样会意味这更大的编码量和更多的测试工作,但是Go与此相反,通常需要编译器静态检查支持
因此 Go 提供了动态语言的优点,却没有其他动态语言在运行时可能发生错误的缺点。
Go 的接口提高了代码的分离度,改善了代码的复用性,使得代码开发过程中的设计模式更容易实现。用 Go 接口还能实现 依赖注入模式
接口的提取 – 设计模式
你不用提前设计出所有的接口;整个设计可以持续演进,而不用废弃之前的决定。类型要实现某个接口,它本身不用改变,你只需要在这个类型上实现新的方法
显式地指明类型实现了某个接口
大部分代码并不使用这样的约束,因为它限制了接口的实用性。
但是有些时候,这样的约束在大量相似的接口中被用来解决歧义。
Go编程中的重要的最佳实践
多态用得越多,代码就相对越少
总结:Go 中的面向对象
我们总结一下前面看到的:Go 没有类,而是松耦合的类型、方法对接口的实现。
OO 语言最重要的三个方面分别是:封装,继承和多态,在 Go 中它们是怎样表现的呢?
封装(数据隐藏):和别的 OO 语言有 4 个或更多的访问层次相比,Go 把它简化为了 2 层(参见 4.2 节的可见性规则):
1)包范围内的:通过标识符首字母小写,对象 只在它所在的包内可见
2)可导出的:通过标识符首字母大写,对象 对所在包以外也可见
类型只拥有自己所在包中定义的方法。
继承:用组合实现:内嵌一个(或多个)包含想要的行为(字段和方法)的类型;多重继承可以通过内嵌多个类型实现
多态:用接口实现:某个类型的实例可以赋给它所实现的任意接口类型的变量。类型和接口是松耦合的,并且多重继承可以通过实现多个接口实现。Go 接口不是 Java 和 C# 接口的变体,而且接口间是不相关的,并且是大规模编程和可适应的演进型设计的关键。
结构体、集合和高阶函数
package main
import "fmt"
type Any interface {}
type Car struct {
Model string
Manufacturer string
BuildYear int
}
type Cars []*Car
func (cs Cars) Process(f func(car *Car)) {
for _, c := range cs {
f(c)
}
}
func (cs Cars) FindAll(f func(car *Car) bool) Cars {
cars := make([]*Car, 0)
cs.Process(func(c *Car) {
if f(c) {
cars = append(cars, c)
}
})
return cars
}
func (cs Cars) Map(f func(car *Car) Any) []Any {
result := make([]Any, len(cs))
ix := 0
cs.Process(func(c *Car) {
result[ix] = f(c)
ix++
})
return result
}
func MakeSortedAppender(manufactures []string) (func(car *Car), map[string]Cars) {
sortedCars := make(map[string]Cars)
for _,m := range manufactures {
sortedCars[m] = make([]*Car, 0)
}
sortedCars["Default"] = make([]*Car, 0)
appender := func(c *Car) {
if _,ok := sortedCars[c.Manufacturer];ok {
sortedCars[c.Manufacturer] = append(sortedCars[c.Manufacturer], c)
} else {
sortedCars["Default"] = append(sortedCars["Default"], c)
}
}
return appender,sortedCars
}
func main() {
// make some cars:
ford := &Car{"Fiesta", "Ford", 2008}
bmw := &Car{"XL 450", "BMW", 2011}
merc := &Car{"D600", "Mercedes", 2009}
bmw2 := &Car{"X 800", "BMW", 2008}
// query:
allCars := Cars([]*Car{ford, bmw, merc, bmw2})
allNewBMWs := allCars.FindAll(func(car *Car) bool {
return (car.Manufacturer == "BMW") && (car.BuildYear > 2010)
})
fmt.Println("AllCars: ", allCars)
fmt.Println("New BMWs: ", allNewBMWs)
//
manufacturers := []string{"Ford", "Aston Martin", "Land Rover", "BMW", "Jaguar"}
sortedAppender, sortedCars := MakeSortedAppender(manufacturers)
allCars.Process(sortedAppender)
fmt.Println("Map sortedCars: ", sortedCars)
BMWCount := len(sortedCars["BMW"])
fmt.Println("We have ", BMWCount, " BMWs")
}