欢迎访问我的博客,目前从事Machine Learning,欢迎交流

【读书笔记&个人心得】第11章:接口与反射

接口 (interface)与反射 (reflection)

接口是什么

接口提供了一种方式来 说明 对象的行为:如果谁能搞定这件事,它就可以用在这儿
接口是Go 的动态语言的部分,Go+断言可以很灵活。

只包括方法

接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。
Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法

type Namer interface {
    Method1(param_list) return_type
    Method2(param_list) return_type
    ...
}

接口命名

接口的名字由方法名加 er 后缀组成,例如 Printer、Reader
还有一些不常用的方式(当后缀 er 不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头(像 .NET 或 Java 中那样)

在 Go 语言中接口可以有值,一个接口类型的变量或一个 接口值 :var ai Namer,ai 是一个多字(multiword)数据结构,它的值是 nil。它本质上是一个指针,虽然不完全是一回事。指向接口值的指针是非法的,它们不仅一点用也没有,还会导致代码错误(不可以再 *ai)

接口变量 ai 包含两部分:receiver 和 method table ptr
此处的方法指针表是通过运行时反射能力构建的。

类型实现接口

类型(比如结构体)可以实现某个接口的方法集,个实现可以描述为,该类型的变量上的每一个具体方法所组成的集合,包含了该接口的方法集。(实现方法集包含了接口方法集)

上面所述类型的变量 实现了 Namer 接口,所以该 变量 可以赋值给 ai(即 receiver 的值),方法表指针(method table ptr)就指向了当前的方法实现。同样,当另一个实现了 Namer 接口的类型的变量被赋给 ai,receiver 的值和方法表指针也会相应改变(指向另一个)

类型不需要显式声明它实现了某个接口:接口被隐式地实现。
多个类型可以实现同一个接口,一个类型可以实现多个接口。

接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。

接口实现的灵活性

第一种:结构体实现接口

即使接口在类型之后才定义,二者处于不同的包中,被单独编译:只要类型实现了接口中的方法,它就实现了此接口。

package main

import "fmt"

type Shaper interface {
	Area() float32
	Side() float32
	DoubleSide() float32
}

type Square struct {
	side float32
}

func main() {
	// var sq1 Square = Square{5}
	// ↑ 导致areaIntf = sq1报错:
	// cannot use sq1 (variable of type Square) as type Shaper in assignment:
	// Square does not implement Shaper (Area method has pointer receiver)
	// 原因在于,Square实现Shaper声明的方法的方式是 指针 接收者,而不是变量接收者,
	// 而要符合要求,就有取地址 或者 使用 new 创建Square变量

	// sq1 := &Square{5} // 对
	sq1 := new(Square) // 对
	sq1.side = 5
	var areaIntf Shaper

	// 由于sq1是Square结构体,Square结构体实现了Shaper声明的方法,
	// 所以,sq1可以赋值给areaIntf接口变量
	areaIntf = sq1
	// shorter,without separate declaration:
	// areaIntf := Shaper(sq1)
	// or even:
	// areaIntf := sq1
	fmt.Printf("The square has area: %f\n", areaIntf.Area())
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

func (sq Square) Side() float32 {
	return sq.side
}

func (sq *Square) DoubleSide() float32 {
	return sq.side * 2
}


areaIntf.Area():口变量里包含了接收者实例的值和指向对应方法表的指针,现在可以直接在 Square 的实例上调用此方法,也可以在接口实例上调用此方法,它使此方法更具有一般性。

针对var sq1 Square的解决方法如下:

package main

import "fmt"

type Shaper interface {
	Area() float32
	Side() float32
	DoubleSide() float32
}

type Square struct {
	side float32
}

func main() {
	var sq1 Square = Square{5}
	var areaIntf Shaper

	areaIntf = &sq1 // 传给接口的应该是指针类型,除非方法实现 全是值类型
	fmt.Printf("The square has area: %f\n", areaIntf.Area())
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

func (sq Square) Side() float32 {
	return sq.side
}

func (sq *Square) DoubleSide() float32 {
	return sq.side * 2
}

第二种:
函数体实现接口。
用type+匿名方法可以定义一种简单接口,如下,Handler是一种简单的接口。其包含ServeHTTP()方法。

type Handler func(*Conn)

func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	s := Server{Handler: h, Handshake: checkOrigin}
	s.serveWebSocket(w, req)
}

Go的多态

这是 多态 的 Go 版本,多态是面向对象编程中一个广为人知的概念:根据当前的类型选择正确的方法,或者说:同一种类型在不同的实例上似乎表现出不同的行为。(因为不同实例上,即使方法名同,但是方法实现不同)

package main

import "fmt"

type Shaper interface {
	Area() float32
}

type Square struct {
	side float32
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

type Rectangle struct {
	length, width float32
}

func (r Rectangle) Area() float32 {
	return r.length * r.width
}

func main() {

	r := Rectangle{5, 3} // Area() of Rectangle needs a value
	q := &Square{5}      // Area() of Square needs a pointer
	// shapes := []Shaper{Shaper(r), Shaper(q)}
	// or shorter
	shapes := []Shaper{r, q}
	fmt.Println("Looping through shapes for area ...")
	for n, _ := range shapes {
		fmt.Println("Shape details: ", shapes[n])
		fmt.Println("Area of this shape is: ", shapes[n].Area())
	}
}

对于接口,值类型或指针类型都是可以接受的

接口作函数参数

类型 --> 结构体 --> 实现方法集 --> 实现接口 --> 作 值 传递给接口参数

package main

import "fmt"

type stockPosition struct {
	ticker     string
	sharePrice float32
	count      float32
}

/* method to determine the value of a stock position */
func (s stockPosition) getValue() float32 {
	return s.sharePrice * s.count
}

type car struct {
	make  string
	model string
	price float32
}

/* method to determine the value of a car */
func (c car) getValue() float32 {
	return c.price
}

/* contract that defines different things that have value */
type valuable interface {
	getValue() float32
}

func showValue(asset valuable) {
	fmt.Printf("Value of the asset is %f\n", asset.getValue())
}

func main() {
	var o valuable = stockPosition{"GOOG", 577.20, 4}
	showValue(o)
	o = car{"BMW", "M3", 66500}
	showValue(o)
}
Value of the asset is 2308.800049
Value of the asset is 66500.000000

接口是什么

不过是给 某类方法集 取的名字而已

练习

定义一个接口 Simpler,它有一个 Get() 方法和一个 Set(),Get() 返回一个整型值,Set() 有一个整型参数。创建一个结构体类型 Simple 实现这个接口。一个抽象类型 Abs(没有字段) 实现同样的功能,它实现接口 Abser,然后在其他类型里内嵌此类型,说明覆写。

接着定一个函数,它有一个 Simpler 类型的参数,分别传Abser和Simpler,调用参数的 Get() 和 Set() 方法。在 main 函数里调用这个函数,看看它是否可以正确运行。

以下展示了一些结论:

1.内嵌可以继承方法因而继承接口实现,然后不必重复实现
2.不同名称但是方法集一致的接口,可以相互赋值

package main

import "fmt"

type Simpler interface {
	Get() int
	Set(i int)
}

type Abser interface {
	Get() int
	Set(i int)
}

type Simple struct {
	age int
}

type Abs struct{}

type NotAbs struct {
	Abs
	age int
}

type NotAbs2 struct {
	NotAbs
}

func (na *Simple) Get() (i int) {
	return na.age
}

func (na *Simple) Set(i int) {
	na.age = i
}

func (na *NotAbs) Get() (i int) {
	i = na.age + 1
	return
}

func (na *NotAbs) Set(i int) {
	na.age = i + 1
}

func showAge(s Simpler) {
	fmt.Println(s.Get())

}

func main() {
	s1 := &Simple{18}
	s2 := &NotAbs{Abs{}, 18}
	s3 := &NotAbs2{NotAbs{Abs{}, 18}}
	var if1 Simpler
	var if2 Abser

	if1 = s1
	showAge(if1)
	if1 = s2
	showAge(if1)
	if1 = s3
	showAge(if1)

	if2 = s1
	showAge(if2)
	if2 = s2
	showAge(if1)
	if2 = s3
	showAge(if1)

}


结果:
18
19
19
18
19
19

接口嵌套接口

一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。


package main

import "fmt"

func main() {
	type ReadWrite interface {
		Read(b int) bool
		Write(b int) bool
	}

	type Lock interface {
		Lock()
		Unlock()
	}

	type File interface {
		ReadWrite
		Lock
		Close()
	}

	type File2 interface {
		Read(b int) bool
		Write(b int) bool
		Lock()
		Unlock()
		Close()
	}

	var if1 ReadWrite
	var if2 Lock
	var if3 File
	var if4 File2

	// if3 = if1 // cannot
	// if4 = if2 // cannot
	if3 = if4
	
	fmt.Println(if1, if2, if3, if4)// <nil> <nil> <nil> <nil>

}

类型断言:如何检测和转换接口变量的类型

一个接口类型的变量 varI 中可以包含任何类型的值,必须有一种方式来检测它的 动态 类型,即运行时在变量中存储的值的实际类型(因为接口接受值的判断依据是方法集而不是类)

通常我们可以使用 类型断言 来测试在某个时刻 varI 是否包含类型 T 的值

if v, ok := varI.(T); ok {  // checked type assertion
    Process(v)
    return
}
// varI is not of type T

如果转换合法,v 是 varI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,ok 是 false,也没有运行时错误发生

package main

import (
	"fmt"
	"math"
)

type Square struct {
	side float32
}

type Circle struct {
	radius float32
}

type Shaper interface {
	Area() float32
}

func main() {
	var areaIntf Shaper
	sq1 := new(Square)
	sq1.side = 5

	areaIntf = sq1
	// Is Square the type of areaIntf?
	if t, ok := areaIntf.(*Square); ok {
		fmt.Printf("The type of areaIntf is: %T\n", t)
	}
	if u, ok := areaIntf.(*Circle); ok {
		fmt.Printf("The type of areaIntf is: %T\n", u)
	} else {
		fmt.Println("areaIntf does not contain a variable of type Circle")
	}
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

func (ci *Circle) Area() float32 {
	return ci.radius * ci.radius * math.Pi
}

如果忽略 areaIntf.(*Square) 中的 * 号,会导致编译错误:impossible type assertion: Square does not implement Shaper (Area method has pointer receiver)

类型判断: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)
}

变量 t 得到了 areaIntf 的值和类型,所有 case 语句中列举的类型(nil 除外)都必须实现对应的接口(在上例中即 Shaper),如果被检测类型没有在 case 语句列举的类型中,就会执行 default 语句。

可以用 type-switch 进行运行时类型分析,但是在 type-switch 不允许有 fallthrough 。

可变长度的任意类型数组 的 类型判断

由于空接口不必实现任何方法集,因此所有类型都可以赋值给它

func classifier(items ...interface{}) {
	for i, x := range items {
		switch x.(type) {
		case bool:
			fmt.Printf("Param #%d is a bool\n", i)
		case float64:
			fmt.Printf("Param #%d is a float64\n", i)
		case int, int64:
			fmt.Printf("Param #%d is a int\n", i)
		case nil:
			fmt.Printf("Param #%d is a nil\n", i)
		case string:
			fmt.Printf("Param #%d is a string\n", i)
		default:
			fmt.Printf("Param #%d is unknown\n", i)
		}
	}
}

// 可以这样调用此方法:classifier(13, -14.3, "BELGIUM", complex(1, 2), nil, false)

作用

可接受任意类型,在处理来自于外部的、类型未知的数据时,比如解析诸如 JSON 或 XML 编码的数据,在类型测试和转换时会非常有用。

在示例 12.17 (xml.go) 中解析 XML 文档时,我们就会用到 type-switch。

练习

返回类型名称

package main

import "fmt"

func main() {
	var i32 int32 = int32(32)
	var i64 int64 = int64(32)
	fmt.Println(testT(i32), testT(i64), testT(2), testT(true), testT(nil), testT(""))

}

func testT(items ...interface{}) (t string) { // 使用了匿名空接口
	for _, x := range items {
		switch x.(type) {
		case bool:
			t = "bool"
		case float64:
			t = "float64"
		case int:
			t = "int"
		case int32:
			t = "int32"
		case int64:
			t = "int64"
		case nil:
			t = "nil"
		case string:
			t = "string"
		default:
			t = "unknown fucking type"

		}
	}
	return
}

测试一个值是否实现了某个接口

type Stringer interface {
    String() string
}

if sv, ok := v.(Stringer); ok {
    fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}

Print() 函数就是如此检测类型是否可以打印自身的

接口小结

接口是一种契约,实现类型必须满足它,它描述了类型的行为,规定类型可以做什么。接口彻底将类型能做什么,以及如何做分离开来,使得相同接口的变量在不同的时刻表现出不同的行为,这就是多态的本质。

使用方法集与接口

作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型值时,这会变得有点复杂,原因是接口变量中存储的具体值是不可寻址的,幸运的是,如果使用不当编译器会给出错误。


我的总结:

其实是对齐的问题,如果你实际实现的方法的接收者是指针类型,那么你调用方法时,就要传指针类型,否则,虽然你确实是实现了接口,传给响应的接口没问题,因为接口不挑值或指针,但是方法挑啊,所以会报个错给你。

方法实现为指针接收者:
用值调(×,因为存储在接口中的值没有地址,因而无法获取地址)
用指针调(√)

方法实现为值接收者:
用值调(√)
用指针调(√,会自动解引用)


编译失败:将一个值赋值给一个接口时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。

package main

import (
	"fmt"
)

type List []int

func (l List) Len() int {
	return len(l)
}

func (l *List) Append(val int) {
	*l = append(*l, val)
}

type Appender interface {
	Append(int)
}

func CountInto(a Appender, start, end int) {
	for i := start; i <= end; i++ {
		a.Append(i)
	}
}

type Lener interface {
	Len() int
}

func LongEnough(l Lener) bool {
	return l.Len()*10 > 42
}

func main() {
	// A bare value
	var lst List 
	// CountInto(lst, 1, 10) // compiler error:
	if LongEnough(lst) { 
		fmt.Printf("- lst is long enough\n")
	}

	// A pointer value
	plst := new(List)
	CountInto(plst, 1, 10) // VALID: Identical receiver type
	if LongEnough(plst) {
		// VALID: a *List can be dereferenced for the receiver
		fmt.Printf("- plst is long enough\n")
	}
}

Go 语言规范定义了接口方法集的调用规则:

类型 *T 的可调用方法集 包含 接受者为 *T 或 T 的所有方法集
类型 T 的可调用方法集 包含接受者为 T 的所有方法
类型 T 的可调用方法集 不包含接受者为 *T 的方法

举例说明接口的深入使用

第一个例子:使用 Sorter 接口排序

sort/sort.go

package sort

type Sorter interface {
	Len() int
	Less(i, j int) bool
	Swap(i, j int)
}

func Sort(data Sorter) {
	for pass := 1; pass < data.Len(); pass++ {
		for i := 0; i < data.Len()-pass; i++ {
			if data.Less(i+1, i) {
				data.Swap(i, i+1)
			}
		}
	}
}

func IsSorted(data Sorter) bool {
	n := data.Len()
	for i := n - 1; i > 0; i-- {
		if data.Less(i, i-1) {
			return false
		}
	}
	return true
}

// Convenience types for common cases
type IntArray []int

func (p IntArray) Len() int           { return len(p) }
func (p IntArray) Less(i, j int) bool { return p[i] < p[j] }
func (p IntArray) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

type StringArray []string

func (p StringArray) Len() int           { return len(p) }
func (p StringArray) Less(i, j int) bool { return p[i] < p[j] }
func (p StringArray) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

// Convenience wrappers for common cases
func SortInts(a []int)       { Sort(IntArray(a)) }
func SortStrings(a []string) { Sort(StringArray(a)) }

func IntsAreSorted(a []int) bool       { return IsSorted(IntArray(a)) }
func StringsAreSorted(a []string) bool { return IsSorted(StringArray(a)) }

main.go

package main

import (
	"fmt"
	"hello_world/sort"
)

func ints() {
	data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
	a := sort.IntArray(data) //conversion to type IntArray
	sort.Sort(a)
	if !sort.IsSorted(a) {
		panic("fails")
	}
	fmt.Printf("The sorted array is: %v\n", a)
}

func strings() {
	data := []string{"monday", "friday", "tuesday", "wednesday", "sunday", "thursday", "", "saturday"}
	a := sort.StringArray(data)
	sort.Sort(a)
	if !sort.IsSorted(a) {
		panic("fail")
	}
	fmt.Printf("The sorted array is: %v\n", a)
}

type day struct {
	num       int
	shortName string
	longName  string
}

type dayArray struct {
	data []*day
}

func (p *dayArray) Len() int           { return len(p.data) }
func (p *dayArray) Less(i, j int) bool { return p.data[i].num < p.data[j].num }
func (p *dayArray) Swap(i, j int)      { p.data[i], p.data[j] = p.data[j], p.data[i] }

func days() {
	Sunday := day{0, "SUN", "Sunday"}
	Monday := day{1, "MON", "Monday"}
	Tuesday := day{2, "TUE", "Tuesday"}
	Wednesday := day{3, "WED", "Wednesday"}
	Thursday := day{4, "THU", "Thursday"}
	Friday := day{5, "FRI", "Friday"}
	Saturday := day{6, "SAT", "Saturday"}
	data := []*day{&Tuesday, &Thursday, &Wednesday, &Sunday, &Monday, &Friday, &Saturday}
	a := dayArray{data}
	sort.Sort(&a)
	if !sort.IsSorted(&a) {
		panic("fail")
	}
	for _, d := range data {
		fmt.Printf("%s ", d.longName)
	}
	fmt.Printf("\n")
}

func main() {
	ints()
	strings()
	days()
}

panic("fail") 用于停止处于在非正常情况下的程序(详细请参考第 13 章),当然也可以先打印一条信息,然后调用 os.Exit(1) 来停止程序。

对于基本类型的排序,标准库已经提供了相关的排序函数,所以不需要我们再重复造轮子了。对于一般性的排序,sort 包定义了一个接口:

type Interface interface {
	Len() int
	Less(i, j int) bool
	Swap(i, j int)
}

这个接口总结了需要用于排序的抽象方法,函数 Sort(data Interface) 用来对此类对象进行排序,可以用它们来实现对其他类型的数据(非基本类型)进行排序。在上面的例子中,我们也是这么做的,不仅可以对 int 和 string 序列进行排序,也可以对用户自定义类型 dayArray 进行排序(通过上面接口的三个办法即可调用sort排序)

练习

自己在实现 使用float64数组/切片 实现Sorter遇到的一些问题

sort.go

package Sort

type Sorter interface {
	Len() int
	Less(i, j int) bool
	Swap(i, j int)
}

func Sort(data Sorter) {
	for i := 0; i < data.Len(); i++ {
		for j := 0; j < data.Len()-i-1; j++ {
			if data.Less(j, j+1) {
				data.Swap(j, j+1)
			}
		}
	}

}

func IsSorted(data Sorter) bool {
	for i := 0; i < data.Len(); i++ {
		for j := 0; j < data.Len()-i-1; j++ {
			if data.Less(j, j+1) {
				return false
			}
		}
	}
	return true
}

main.go

package main

import (
	"fmt"
	"hello_world2/Sort"
)

type Float64Array []float64

// type Float64Array [10]float64 // 类型别名只能在这里定义,无法导出。结构体可以

func main() {
	f64_arr := Float64Array{1.2, 3.1, 21.8, 55, 9, 66, 1.99, 113.8, 53, 0}

	// Sort.Sort(f64_arr) // Float64Array是切片时
	Sort.Sort(&f64_arr) // Float64Array是数组或切片
	fmt.Println(Sort.IsSorted(&f64_arr), f64_arr)
}

func (arr Float64Array) Len() int {
	return len(arr)
}

func (arr Float64Array) Less(i, j int) bool {
	return arr[i] < arr[j]
}

// 如果Float64Array是数组,这里必须是指针,因为数组是值类型
func (arr *Float64Array) Swap(i, j int) {
	(*arr)[i], (*arr)[j] = (*arr)[j], (*arr)[i]
}

// 如果Float64Array是切片,那么这里的接收者就可以使用值或指针
// func (arr Float64Array) Swap(i, j int) {
// 	arr[i], arr[j] = arr[j], arr[i]
// }

第二个例子:读和写

io 包提供了用于读和写的接口 io.Reader 和 io.Writer:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

只要类型实现了读写接口,提供 Read 和 Write 方法,就可以从它读取数据,或向它写入数据。
一个对象要是可读的,它必须实现 io.Reader 接口,这个接口只有一个签名是 Read(p []byte) (n int, err error) 的方法,它从调用它的对象上读取数据,并把读到的数据放入参数中的字节切片中,然后返回读取的字节数和一个 error 对象,如果没有错误发生返回 nil,如果已经到达输入的尾端,会返回 io.EOF("EOF"),如果读取的过程中发生了错误,就会返回具体的错误信息。
类似地,一个对象要是可写的,它必须实现 io.Writer 接口,这个接口也只有一个签名是 Write(p []byte) (n int, err error) 的方法,它将指定字节切片中的数据写入调用它的对象里,然后返回实际写入的字节数和一个 error 对象(如果没有错误发生就是 nil)

带缓冲的读写 bufio

io 包里的 Readers 和 Writers 都是不带缓冲的,bufio 包里提供了对应的带缓冲的操作,在读写 UTF-8 编码的文本文件时它们尤其有用。在第 12 章我们会看到很多在实战中使用它们的例子

空接口

空接口或者最小接口 不包含任何方法,它对实现不做任何要求:

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 string
	age  int
}

type Any interface{}

func main() {
	var val Any
	val = 5
	fmt.Printf("val has the value: %v\n", val)
	val = str
	fmt.Printf("val has the value: %v\n", val)
	pers1 := new(Person)
	pers1.name = "Rob Pike"
	pers1.age = 55
	val = pers1
	fmt.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)
	}
}

输出:
val has the value: 5
val has the value: ABC
val has the value: &{Rob Pike 55}
Type pointer to Person *main.Person

空接口在 type-switch 中联合 lambda 函数的用法

package main

import "fmt"

type specialString string

var whatIsThis specialString = "hello"

func TypeSwitch() {
	testFunc := func(any interface{}) {
		switch v := any.(type) {
		case bool:
			fmt.Printf("any %v is a bool type", v)
		case int:
			fmt.Printf("any %v is an int type", v)
		case float32:
			fmt.Printf("any %v is a float32 type", v)
		case string:
			fmt.Printf("any %v is a string type", v)
		case specialString:
			fmt.Printf("any %v is a special String!", v)
		default:
			fmt.Println("unknown type!")
		}
	}
	testFunc(whatIsThis)
}

func main() {
	TypeSwitch()
}

构建通用类型或包含不同类型变量的数组

package main

import (
	"fmt"
)

type Element interface{}
type Vector struct {
	a []Element
}

func main() {
	vc := new(Vector)
	a := []float32{1.2, 3.1}
	b := []string{"zhangsan", "lisi"}
	// v.a = []float32{1.2, 3.1, 21.8, 55, 9, 66, 1.99, 113.8, 53, 0}
	//error : cannot use ([]float32 literal) (value of type []float32) as []Element value in assignment
	// 参考资料:https://blog.csdn.net/huwei1844352682/article/details/115016507
	vc.a = make([]Element, len(a)+len(b))
	for i, v := range a {
		// vc.a[i] = v // ok
		vc.Set(i, v)
	}

	for i, v := range b {
		// vc.a[len(a)+i] = v  //ok
		vc.Set(len(a)+i, v)
	}

	fmt.Println(vc, vc.At(0))
}

func (p *Vector) At(i int) Element {
	return p.a[i]
}

func (p *Vector) Set(i int, e Element) {
	p.a[i] = e
}

Vector 中存储的所有元素都是 Element 类型,要得到它们的原始类型(unboxing:拆箱)需要用到类型断言。TODO:The compiler rejects assertions guaranteed to fail,类型断言总是在运行时才执行,因此它会产生运行时错误。

复制数据切片至空接口切片

若想将切片中的数据复制到一个空接口切片中

var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = dataSlice

可惜不能这么做,编译时会出错:cannot use dataSlice (type []myType) as type []interface { } in assignment。

原因是它们俩在内存中的布局是不一样的
参考资料:https://github.com/golang/go/wiki/InterfaceSlice

我的总结:
首先,[]interface{}不是空接口而是切片,虽然它的元素是空接口。
在编译时,[]interface{}有特定的内存分布,每一个空接口有两个字组成,一个是所含内容的类型,另一个字是所含内容本身或指向内容的指针。
因此,长度为N的空接口切片[]interface{}由一大块长度为2*N的数据作支持,而支持一般切片的数据块长度是

N*sizeof(MyType) 

结果就是你不能把[]MyType 赋值给 []interface{} ,因为背后的数据是不同的

如果一定要这样赋值,则只能通过制作切片的副本

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
	interfaceSlice[i] = d
}

通用类型的节点数据结构

构建二叉树:
在 10.1 中我们遇到了诸如列表和树这样的数据结构,在它们的定义中使用了一种叫节点的递归结构体类型,节点包含一个某种类型的数据字段。现在可以使用空接口作为数据字段的类型,这样我们就能写出通用的代码。下面是实现一个二叉树的部分代码:通用定义、用于创建空节点的 NewNode 方法,及设置数据的 SetData 方法。

package main

import (
	"fmt"
)

type Node struct {
	left  *Node
	data  interface{}
	right *Node
}

func NewNode(left, right *Node) *Node {

	return &Node{left, nil, right}
}

func (n *Node) SetData(data interface{}) {
	n.data = data
}

func main() {
	root := NewNode(nil, nil)
	root.SetData("root node")
	a := NewNode(nil, nil)
	a.SetData("left node")
	b := NewNode(nil, nil)
	b.SetData("right node")
	root.left = a
	root.right = b
	fmt.Printf("%v %v %v\n", a, b, root) // Output: &{0x125275f0 root node 0x125275e0}
}

接口到接口

一个接口的值可以赋值给另一个接口变量,只要底层类型实现了必要的方法。这个转换是在运行时进行检查的,转换失败会导致一个运行时错误:这是 Go 语言动态的一面,可以拿它和 Ruby 和 Python 这些动态语言相比较。

所谓只要底层实现,就是说 可以一层层传导的,使用断言时,会一层层查到底层

package main

import (
	"math"
)

type AbsInterface interface {
	Abs() float64 // 绝对值
}

type SqrInterface interface {
	Sqr() float64 // 开平方
}

var any interface{}

type Point struct {
	i float64
}

func (p *Point) Abs() float64 {
	return math.Abs(p.i)
}

func (p *Point) Sqr() float64 {
	return math.Sqrt(p.i)
}

func main() {
	var ai AbsInterface
	var si SqrInterface
	pp := new(Point)

	any = pp                // everything satisfies empty
	ai = any.(AbsInterface) // underlying(潜在的)value pp implements Abs()
	si = ai.(SqrInterface)  // underlying(潜在的)value pp implements Abs()

	// *Point implements empty set*Point 实现空集
	// Note: statically checkable so type assertion not necessary.  静态可检查,因此不需要类型断言
	any = si
}

判断一个方法是否可以调用,避免出错

下面是函数调用的一个例子:

type myPrintInterface interface {
	print()
}

func f3(x myInterface) {
	x.(myPrintInterface).print() // type assertion to myPrintInterface
}

x 转换为 myPrintInterface 类型是完全动态的:只要 x 的底层类型(动态类型)定义了 print 方法这个调用就可以正常运行(译注:若 x 的底层类型未定义 print 方法,此处类型断言会导致 panic,最佳实践应该为 if mpi, ok := x.(myPrintInterface); ok { mpi.print() },参考 11.3 章节)。

type myPrintInterface interface {
	print()
}

func f3(x myInterface) {
	if mpi, ok := x.(myPrintInterface); ok { mpi.print() }
}

反射

谨慎使用

反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。反射可以在运行时检查类型和变量,例如:它的大小、它的方法以及它能“动态地”调用这些方法。这对于没有源代码的包尤其有用。
这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。

类型和值

变量的最基本信息就是类型和值:反射包的 Type 用来表示一个 Go 类型,反射包的 Value 为 Go 值提供了反射接口。

两个简单的函数,reflect.TypeOf 和 reflect.ValueOf,返回被检查对象的类型和值。例如,x 被定义为:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64,reflect.ValueOf(x) 返回

package main

import (
	"fmt"
	"reflect"
)

func main() {
	x := 1123
	fmt.Println(reflect.ValueOf(x)) // 1123
}

实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

Type()/Kind()/Int()/Float()

reflect.Type 和 reflect.Value 都有许多方法用于检查和操作它们。一个重要的例子是 Value 有一个 Type() 方法返回 reflect.Value 的 Type 类型。另一个是 Type 和 Value 都有 Kind() 方法返回一个常量来表示类型:Uint、Float64、Slice 等等。同样 Value 有叫做 Int() 和 Float() 的方法可以获取存储在内部的值(跟 int64 和 float64 一样)

这东西就是说,把变量放到reflect里转换一下,出来的值自带很多很方便的方法可以查看各种东西

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float32 = 32.198
	var a int = 32
	a1 := reflect.ValueOf(a)
	x1 := reflect.TypeOf(x)
	x2 := reflect.ValueOf(x)

	fmt.Printf("%v %d %v %v %v \n", x1.Kind(), x2.Kind(), x2.Type(), a1.Int(), x2.Float())
	fmt.Println(a1.Interface(), x2.Interface())
}

// 输出:float32 13 float32 32 32.198001861572266

Kind()常量

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)

Valueof 返回的变量 a,x2 的 Interface() 方法可以得到还原(接口)值,所以可以这样打印 a,x2

fmt.Println(a1.Interface(), x2.Interface()) // 32 32.198

例子2

x 是一个 float64 类型的值,reflect.ValueOf(x).Float() 返回这个 float64 类型的实际值;同样的适用于 Int(), Bool(), Complex(), String()

// blog: Laws of Reflection
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 3.4
	fmt.Println("type:", reflect.TypeOf(x))
	v := reflect.ValueOf(x)
	fmt.Println("value:", v)
	fmt.Println("type:", v.Type())
	fmt.Println("kind:", v.Kind())
	fmt.Println("value:", v.Float())
	fmt.Println(v.Interface())
	fmt.Printf("value is %5.2e\n", v.Interface())
	y := v.Interface().(float64)
	fmt.Println(y)
}

输出:
type: float64
value: 3.4
type: float64
kind: float64
value: 3.4
3.4
value is 3.40e+00
3.4

通过反射修改(设置)值

使用CanSet()检测是否可改值,因为ValueOf()返回的是值或引用的拷贝,不可直接改,所以首先要向ValueOf()传地址,然后使用Elem() 函数,然后再调SetFloat()等。

是否可设置是 Value 的一个属性,并且不是所有的反射值都有这个属性:可以使用 CanSet() 方法测试是否可设置。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 3.4
	v := reflect.ValueOf(x)
	// setting a value:
	// v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable value
	fmt.Println("settability of v:", v.CanSet())
	v = reflect.ValueOf(&x) // 第一步:取地址
	fmt.Println("type of v:", v.Type())
	fmt.Println("settability of v:", v.CanSet())//第二步:判断是否可改值
	v = v.Elem()//第三步:调Elem()
	fmt.Println("The Elem of v is: ", v)
	fmt.Println("settability of v:", v.CanSet())
	v.SetFloat(3.1415) // 第四步:改值
	fmt.Println(v.Interface())
	fmt.Println(v)
}

反射结构

有些时候需要反射一个结构类型。NumField() 方法返回结构内的字段数量;通过一个 for 循环用索引取得每个字段的值 Field(i)。

我们同样能够调用签名在结构上的方法,例如,使用索引 n 来调用:Method(n).Call(nil)
参考资料:https://www.cnblogs.com/52php/p/6337420.html

package main

import (
	"fmt"
	"reflect"
)

type NotknownType struct {
	s1, s2, s3 string
}

type NotknownType2 struct {
	S1, S2, S3 string
}

func (n NotknownType) String() string {
	return n.s1 + " - " + n.s2 + " - " + n.s3
}

func (n NotknownType2) String(s string) string {
	return "the" + s + ":" + n.S1 + " - " + n.S2 + " - " + n.S3
}

// variable to investigate:
var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}
var secret2 interface{} = NotknownType2{"Ada", "Go", "Oberon"}

func main() {
	value := reflect.ValueOf(secret) // <main.NotknownType Value>
	typ := reflect.TypeOf(secret)    // main.NotknownType
	// 或者:
	// typ := value.Type()  // main.NotknownType
	fmt.Println(typ)
	knd := value.Kind() // struct
	fmt.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))

		// value.Field(i).SetString("C#")
		// error: panic: reflect.Value.SetString using value obtained using unexported field
	}

	// call the first method, which is String():
	results := value.Method(0).Call(nil)
	fmt.Println(results) // [Ada - Go - Oberon]

	value2 := reflect.ValueOf(&secret2)
	fmt.Printf("value2.CanSet(): %v\n", value2.CanSet()) // false 不可以设置值
	// value2.Field(0).SetString("#2") // error

	value2 = reflect.ValueOf(secret2)
	params := make([]reflect.Value, 1)
	params[0] = reflect.ValueOf("value2")
	results2 := value2.Method(0).Call(params)
	fmt.Println(results2)
}

以下却可以设置

package main

import (
	"fmt"
	"reflect"
)

type T struct {
	A int
	B string
}

func main() {
	t := T{23, "skidoo"}
	s := reflect.ValueOf(&t).Elem()
	typeOfT := s.Type()
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		fmt.Printf("%d: %s %s = %v\n", i,
			typeOfT.Field(i).Name, f.Type(), f.Interface())
	}
	s.Field(0).SetInt(77)
	s.Field(1).SetString("Sunset Strip")
	fmt.Println("t is now", t)
}

总结:要想结构反射可以设置值,字段必须使用纯大写字母,不能使用字母+数字

Printf() 和反射

在 Go 语言的标准库中,前几节所述的反射的功能被大量地使用

func Printf(format string, args ... interface{}) (n int, err error)

Printf() 中的 ... 参数为空接口类型。Printf() 使用反射包来解析这个参数列表。所以,Printf() 能够知道它每个参数的类型。因此格式化字符串中只有 %d 而没有 %u 和 %ld,因为它知道这个参数是 unsigned 还是 long。这也是为什么 Print() 和 Println() 在没有格式字符串的情况下还能如此漂亮地输出

简单版Print

package main

import (
	"fmt"
	"os"
	"strconv"
)

type Stringer interface {
	String() string
}

type Celsius float64

func (c Celsius) String() string {
	return strconv.FormatFloat(float64(c), 'f', 1, 64) + " °C"
}

type Day int

var dayName = []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}

func (day Day) String() string {
	return dayName[day]
}

func print(args ...interface{}) {
	for i, arg := range args {
		if i > 0 {
			os.Stdout.WriteString(" ")
		}
		switch a := arg.(type) { // type switch
		case Stringer:
			fmt.Printf("#Stringer:")
			os.Stdout.WriteString(a.String())
		case int:
			fmt.Printf("#int:")
			os.Stdout.WriteString(strconv.Itoa(a))
		case string:
			fmt.Printf("#string:")
			os.Stdout.WriteString(a)
		default:
			os.Stdout.WriteString("???")
		}
	}
}

func main() {

	print(Day(1), 1234, "was", Celsius(18.36)) // Tuesday was 18.4 °C
}

输出:

Stringer:Tuesday #int:1234 #string:was #Stringer:18.4 °C

注意,只要是实现了String()函数,则被认为实现了Stringer接口,那么传进来时就是Stringer接口类型,如果有两个只是名称不同,方法集一致的接口呢?

提示:switch type 不允许使用fallthrough

package main

import (
	"fmt"
	"os"
	"strconv"
)

type Stringer2 interface {
	String() string
}

type Stringer interface {
	String() string
}

type Celsius float64

func (c Celsius) String() string {
	return strconv.FormatFloat(float64(c), 'f', 1, 64) + " °C"
}

type Day int

var dayName = []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}

func (day Day) String() string {
	return dayName[day]
}

func print(args ...interface{}) {
	for i, arg := range args {
		if i > 0 {
			os.Stdout.WriteString(" ")
		}
		switch a := arg.(type) { // type switch
		case Stringer:
			fmt.Printf("#Stringer:")
			os.Stdout.WriteString(a.String())
		case Stringer2:
			fmt.Printf("#Stringer2:")
			os.Stdout.WriteString(a.String())
		case int:
			fmt.Printf("#int:")
			os.Stdout.WriteString(strconv.Itoa(a))
		case string:
			fmt.Printf("#string:")
			os.Stdout.WriteString(a)
		default:
			os.Stdout.WriteString("???")
		}
	}
}

func main() {

	print(Day(1), 1234, "was", Celsius(18.36)) // Tuesday was 18.4 °C
}

输出:

Stringer:Tuesday #int:1234 #string:was #Stringer:18.4 °C 或

Stringer2:Tuesday #int:1234 #string:was #Stringer2:18.4 °C

其实,Stringer和Stringer2 接口是一个东西,因为方法集一致,至于会落到哪个分支,完全是case Stringer和case Stringer2在switch中的顺序问题,而与type定义接口的顺序无关

接口与动态类型

在经典的面向对象语言(像 C++,Java 和 C#)中数据和方法被封装为类的概念:类包含它们两者,并且不能剥离。

Go 没有类:数据(结构体或更一般的类型)和方法是一种松耦合的正交关系。

接收一个(或多个)接口类型作为参数的函数,其实参可以是任何实现了该接口的类型的变量。 实现了某个接口的类型可以被传给任何以此接口为参数的函数。

package main

import "fmt"

type IDuck interface {
	Quack()
	Walk()
}

func DuckDance(duck IDuck) {
	for i := 1; i <= 3; i++ {
		duck.Quack()
		duck.Walk()
	}
}

type Bird struct {
	// ...
}

func (b *Bird) Quack() {
	fmt.Println("I am quacking!")
}

func (b *Bird) Walk()  {
	fmt.Println("I am walking!")
}

func main() {
	b := new(Bird)
	DuckDance(b)
}

如果 Bird 没有实现 Walk()(把它注释掉),会得到一个编译错误:

cannot use b (type *Bird) as type IDuck in function argument:
*Bird does not implement IDuck (missing Walk method)

但是 Python 和 Ruby 会以运行时错误结束

变量赋值给接口

像 Python,Ruby 这类语言,动态类型是延迟绑定的(在运行时进行):方法只是用参数和变量简单地调用,然后在运行时才解析(它们很可能有像 responds_to 这样的方法来检查对象是否可以响应某个方法,但是这也意味着更大的编码量和更多的测试工作)

Go 的实现与此相反,通常需要编译器静态检查的支持:当变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数。如果方法调用作用于像 interface{} 这样的“泛型”上,你可以通过类型断言(参见 11.3 节)来检查变量是否实现了相应接口。

package main

import (
	"fmt"
	"strconv"
)

type Stringer interface {
	String() string
}

type Celsius float64

func (c Celsius) String() string {
	return strconv.FormatFloat(float64(c), 'f', 1, 64) + " °C"
}

func print(a Stringer) {
	fmt.Println(a.String())
}

func main() {
	var c Celsius
	var c2 float64 = 32.44
	c = Celsius(37.22)
	print(c)  //37.2 °C
	print(c2) //error : cannot use c2 (variable of type float64) as type Stringer in argument to print:
	float64 does not implement Stringer (missing String method)
}

基于断言,可以实现判断一个xml是否实现了 我们自定义的接口 把一个流写为xml,使用断言判断xml是否实现了我们的接口(有写方法),是则调用我们自己定义的写方法,否则,调用内建的写方法

// Exported XML streaming function.
func StreamXML(v interface{}, w io.Writer) error {
	if xw, ok := v.(xmlWriter); ok {
		// It’s an  xmlWriter, use method of asserted type.
		return xw.WriteXML(w)
	}
	// No implementation, so we have to use our own function (with perhaps reflection):
	return encodeToXML(v, w)
}

// Internal XML encoding function.
func encodeToXML(v interface{}, w io.Writer) error {
	// ...
}

接口的妙用

1.公共:可以提取一些公共行为(方法集)作为新接口,然后给其命名,作公共接口。我们任何一个类型,只要A实现了这个接口的要求方法集,A就是打开了潘多拉的盒子,以后所有以此接口为参数进行对接的函数BCD都将能接受A

2.演进:你不用提前设计出所有的接口;整个设计可以持续演进,而不用废弃之前的决定。类型要实现某个接口,它本身不用改变,你只需要在这个类型上继续实现新的方法。

//multi_interfaces_poly.go
package main

import "fmt"

type Shaper interface {
	Area() float32
}

type TopologicalGenus interface {
	Rank() int
}

type Square struct {
	side float32
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

func (sq *Square) Rank() int {
	return 1
}

type Rectangle struct {
	length, width float32
}

func (r Rectangle) Area() float32 {
	return r.length * r.width
}

func (r Rectangle) Rank() int {
	return 2
}

func main() {
	r := Rectangle{5, 3} // Area() of Rectangle needs a value
	q := &Square{5}      // Area() of Square needs a pointer
	shapes := []Shaper{r, q}
	fmt.Println("Looping through shapes for area ...")
	for n, _ := range shapes {
		fmt.Println("Shape details: ", shapes[n])
		fmt.Println("Area of this shape is: ", shapes[n].Area())
	}
	topgen := []TopologicalGenus{r, q}
	fmt.Println("Looping through topgen for rank ...")
	for n, _ := range topgen {
		fmt.Println("Shape details: ", topgen[n])
		fmt.Println("Topological Genus of this shape is: ", topgen[n].Rank())
	}
	show(topgen)
}

func show(s []TopologicalGenus) {
	for n, _ := range s {
		fmt.Println("Shape details: ", s[n])
		fmt.Println("Topological Genus of this shape is: ", s[n].Rank())
	}
}

显式地指明类型实现了某个接口

其实只是一种假的指明,没有语法作用,只是用来解决歧义

type Fooer interface {
	Foo()
	ImplementsFooer()
}

类型 Bar 必须实现 ImplementsFooer 方法来满足 Fooer 接口,以清楚地记录这个事实。

type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {} 

一看有方法ImplementsFooer或Foo,就知道是实现了Fooer接口。

大部分代码并不使用这样的约束,因为它限制了接口的实用性。但是有些时候,这样的约束在大量相似的接口中被用来解决歧义。

空接口和函数重载

在 6.1 节中, 我们看到函数重载是不被允许的。在 Go 语言中函数重载可以用可变参数 ...T 作为函数最后一个参数来实现(参见 6.3 节)。如果我们把 T 换为空接口,那么可以知道任何类型的变量都是满足 T (空接口)类型的,这样就允许我们传递任何数量任何类型的参数给函数,即重载的实际含义。(不过,函数重载除了接受任何类型参数外,还会自动调用不同的代码,这个在Go只能自己写)

函数 fmt.Printf 就是这样做的:

fmt.Printf(format string, a ...interface{}) (n int, errno error)

这个函数通过枚举 slice 类型的实参动态确定所有参数的类型,并查看每个类型是否实现了 String() 方法,如果是就用于产生输出信息。我们可以回到 11.10 节查看这些细节。

接口的继承

当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。(有时候我们只是用他的方法,通过传参即可使用现有类型,不一定只能通过接收者)

type Task struct {
	Command string
	*log.Logger
}

这个类型的工厂方法像这样:

func NewTask(command string, logger *log.Logger) *Task {
	return &Task{command, logger}
}

当 log.Logger 实现了 Log() 方法后,Task 的实例 task 就可以调用该方法:

task.Log()

类型可以通过继承多个接口来提供像多重继承一样的特性:

type ReaderWriter struct {
	*io.Reader
	*io.Writer
}

上面概述的原理被应用于整个 Go 包,多态用得越多,代码就相对越少(参见 12.8 节)。这被认为是 Go 编程中的重要的最佳实践。

练习

在练习 7.13 中我们定义了一个 map() 函数来使用 int 切片 (map_function.go)。

通过空接口和类型断言,现在我们可以写一个可以应用于许多类型的泛型的 map() 函数,为 int 和 string 构建一个把 int 值加倍和将字符串值与其自身连接(译者注:即 "abc" 变成 "abcabc" )的 map() 函数 mapFunc()。

提示:为了可读性可以定义一个 interface{} 的别名,比如:type obj interface{}。

package main

import "fmt"

func main() {
	fmt.Println(mapFunc(1), mapFunc("123")) // 2 123123
}

func mapFunc(t interface{}) (r interface{}) {

	switch a := t.(type) {
	case int:
		r = a * 2
	case string:
		r = string(a) + string(a)
	default:
		r = nil
	}
	return
}

稍微改变练习 1,允许 mapFunc() 接收不定数量的 items。

package main

import "fmt"

func main() {
	for _, v := range mapFunc(1, "123", nil, 2.33, false) {
		if v != nil {
			fmt.Println(v)
		}
	}

}

func mapFunc(t ...interface{}) (r []interface{}) {

	r1 := make([]interface{}, len(t))

	for i, v := range t {

		switch a := v.(type) {
		case int:
			r1[i] = a * 2
			i++
		case string:
			r1[i] = string(a) + string(a)
		default:
			r1[i] = nil
		}
	}

	r = r1
	return
}

总结:Go 中的面向对象

我们总结一下前面看到的:Go 没有类,而是松耦合的类型、方法对接口的实现。

OO 语言最重要的三个方面分别是:封装、继承和多态,在 Go 中它们是怎样表现的呢?

封装(数据隐藏):和别的 OO 语言有 4 个或更多的访问层次相比,Go 把它简化为了 2 层(参见 4.2 节的可见性规则):

1)包范围内的:通过标识符首字母小写,对象只在它所在的包内可见

2)可导出的:通过标识符首字母大写,对象对所在包以外也可见

类型只拥有自己所在包中定义的方法。

继承:用组合实现:内嵌一个(或多个)包含想要的行为(字段和方法)的类型;多重继承可以通过内嵌多个类型实现
多态:用接口实现:某个类型的实例可以赋给它所实现的任意接口类型的变量。类型和接口是松耦合的,并且多重继承可以通过实现多个接口实现。Go 接口不是 Java 和 C# 接口的变体,而且接口间是不相关的,并且是大规模编程和可适应的演进型设计的关键。(多态就是一种抽象,把一些公共行为抽象出来,就好比我每天城轨都去深圳,但是,车上的人都不同,只要买票就能上车去深圳,公共行为就是每天通过城轨去深圳,至于是男人去深圳还是女人还是老人无所谓,买票就好)

结构体、集合和高阶函数

高阶函数

其实就是参数是函数的函数
通常你在应用中定义了一个结构体,那么你也可能需要这个结构体的(指针)对象集合,比如:

type Any interface{}
type Car struct {
	Model        string
	Manufacturer string
	BuildYear    int
	// ...
}

type Cars []*Car

1)定义一个通用的 Process() 函数,它接收一个作用于每一辆 car 的 f 函数作参数:(我们可以看到f无返回值,因此其传参是指针)

// Process all cars with the given function f:
func (cs Cars) Process(f func(car *Car)) {
	for _, c := range cs {
		f(c)
	}
}

2)在上面的基础上,实现一个查找函数来获取子集合,并在 Process() 中传入一个闭包执行(这样就可以访问局部切片 cars):

package main

import "fmt"

type Car struct {
	name string
}

type Cars []*Car

func equal(car *Car, aCar *Car) bool {
	return car.name == aCar.name
}

func main() {
	var cars Cars = Cars([]*Car{&Car{"benz"}, &Car{"baoma"}, &Car{"woerwo"}})
	car := &Car{"baoma"}
	finded := cars.FindAll(car, equal)
	for _, v := range finded {
		fmt.Printf("%v", v) // &{baoma}
	}
}

// Find all cars matching a given criteria.
func (cs Cars) FindAll(car *Car, f func(car *Car, aCar *Car) bool) Cars {
	cars := make([]*Car, 0)
	for _, v := range cs {
		if v2 := f(car, v); v2 {
			cars = append(cars, v)
		}
	}

	return cars
}

3)实现对应作用的功效 (Map-functionality),从每个 car 对象当中产出某些东西:(其实就是遍历一个切片,把每个元素丢去f执行一下,把结果收集起来)

// Process cars and create new data.
func (cs Cars) Map(f func(car *Car) Any) []Any {
	result := make([]Any, 0)
	ix := 0
	cs.Process(func(c *Car) {
		result[ix] = f(c)
		ix++
	})
	return result
}

4)在2的基础上,定义一个找宝马新车的函数

allNewBMWs := allCars.FindAll(func(car *Car) bool {
	return (car.Manufacturer == "BMW") && (car.BuildYear > 2010)
})

5)我们也可以根据参数返回不同的函数。也许我们想根据不同的厂商添加汽车到不同的集合,但是这(这种映射关系)可能会是会改变的。所以我们可以定义一个函数来产生特定的添加函数和 map 集:

func MakeSortedAppender(manufacturers []string)(func(car *Car),map[string]Cars) {
	// Prepare maps of sorted cars.
	sortedCars := make(map[string]Cars)
	for _, m := range manufacturers {
		sortedCars[m] = make([]*Car, 0)
	}
	sortedCars["Default"] = make([]*Car, 0)
	// Prepare appender function:
	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
}

现在我们可以用它把汽车分类为独立的集合,像这样:

manufacturers := []string{"Ford", "Aston Martin", "Land Rover", "BMW", "Jaguar"}
sortedAppender, sortedCars := MakeSortedAppender(manufacturers)
allUnsortedCars.Process(sortedAppender)
BMWCount := len(sortedCars["BMW"])

完整版:

// cars.go
package main

import (
	"fmt"
)

type Any interface{}
type Car struct {
	Model        string //设计
	Manufacturer string //制造商
	BuildYear    int // 制造年份
	// ...
}
type Cars []*Car // 汽车列表

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

// Process all cars with the given function f:
func (cs Cars) Process(f func(car *Car)) {
	for _, c := range cs {
		f(c)
	}
}

// Find all cars matching a given criteria.
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
}

// Process cars and create new data.
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
}

// 这个就是把车按制造商归类,每一类使用切片存储,制造商对应类的切片用map实现
func MakeSortedAppender(manufacturers []string) (func(car *Car), map[string]Cars) {
	// Prepare maps of sorted cars.
	sortedCars := make(map[string]Cars)

	for _, m := range manufacturers {
		sortedCars[m] = make([]*Car, 0)
	}
	sortedCars["Default"] = make([]*Car, 0)

	// Prepare appender function:
	appender := func(c *Car) {
		if _, ok := sortedCars[c.Manufacturer]; ok { // ok是map是否存在该元素
			sortedCars[c.Manufacturer] = append(sortedCars[c.Manufacturer], c)
		} else {
			sortedCars["Default"] = append(sortedCars["Default"], c)
		}
	}
	return appender, sortedCars
}

输出:
AllCars: [0xf8400038a0 0xf840003bd0 0xf840003ba0 0xf840003b70]
New BMWs: [0xf840003bd0]
Map sortedCars: map[Default:[0xf840003ba0] Jaguar:[] Land Rover:[] BMW:[0xf840003bd0 0xf840003b70] Aston Martin:[] Ford:[0xf8400038a0]]
We have 2 BMWs

posted @ 2023-03-01 11:57  有蚊子  阅读(37)  评论(0编辑  收藏  举报