Go接口与反射

接口定义

》接口中不能包含变量
》接口定义了一组方法,但是这些方法不包含实现代码在这里插入图片描述
可以看到,后面是一个方法指针表,是通过运行时反射能力构建

》接口可以有值,一个接口类型的变量

// 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")
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值