07-抚摸抽象边界:Golang 接口的多彩展现-LMLPHP


Go语言里面设计最精妙的应该算 interface ,它让面向对象,内容组织实现非常的方便

什么是interface

简单的说, interface 是一组 method 签名的组合,通过 interface 来定义对象的一组行为。
前面例子中 StudentEmployee 都能 SayHi ,虽然他们的内部实现不一样,但是那不重要,重要的是他们都能 say hi
继续做更多的扩展, StudentEmployee 实现另一个方法 Sing ,然后 Student 实现方法 BorrowMoneyEmployee 实现 SpendSalary
这样 Student 实现了三个方法: SayHiSingBorrowMoney ;而 Employee 实现了 SayHiSingSpendSalary
上面这些方法的组合称为 interface (被对象 StudentEmployee 实现)。例如 StudentEmployee 都实现了 interface : SayHiSing ,也就是这两个对象是该 interface 类型。而 Employee 没有实现这个 interface:SayHiSingBorrowMoney ,因为 Employee 没有实现 BorrowMoney 这个方法。

interface类型

interface 类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子:

type Human struct {
	name string
	age int
	phone string
}
type Student struct {
	Human //匿名字段Human
	school string
	loan float32
}
type Employee struct {
	Human //匿名字段Human
	company string
	money float32
}
//Human对象实现Sayhi方法
func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
// Human对象实现Sing方法
func (h *Human) Sing(lyrics string) {
	fmt.Println("La la, la la la, la la la la la...", lyrics)
}
//Human对象实现Guzzle方法
func (h *Human) Guzzle(beerStein string) {
	fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}
// Employee重载Human的Sayhi方法
func (e *Employee) SayHi() {
	fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
	e.company, e.phone) //此句可以分成多行
}
//Student实现BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
	s.loan += amount // (again and again and...)
}
//Employee实现SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {
	e.money -= amount // More vodka please!!! Get me through the day!
}
// 定义interface
type Men interface {
	SayHi()
	Sing(lyrics string)
	Guzzle(beerStein string)
}
type YoungChap interface {
	SayHi()
	Sing(song string)
	BorrowMoney(amount float32)
}
type ElderlyGent interface {
	SayHi()
	Sing(song string)
	SpendSalary(amount float32)
}

通过上面的代码可以知道,interface 可以被任意的对象实现。看到上面的 Men interfaceHumanStudentEmployee 实现。
同理,一个对象可以实现任意多个 interface,例如上面的 Student 实现了 MenYoungChap 两个 interface
最后,任意的类型都实现了空 interface (这样定 义:interface{}),也就是包含 0methodinterface

interface值

那么 interface 里面到底能存什么值呢?如果定义了一个 interface 的变量,那么这个变量里面可以存实现这个 interface 的任意类型的对象。例如上面例子中,定义了一个 Men interface 类型的变量 m,那么 m 里面可以存 HumanStudent 或者 Employee 值。
因为 m 能够持有这三种类型的对象,所以可以定义一个包含 Men 类型元素的 slice,这个 slice 可以被赋予
实现了 Men 接口的任意结构的对象,这个和传统意义上面的 slice 有所不同。
来看一下下面这个例子:

package main
import "fmt"

type Human struct {
	name string
	age int
	phone string
}
type Student struct {
	Human //匿名字段
	school string
	loan float32
}
type Employee struct {
	Human //匿名字段
	company string
	money float32
}
//Human实现SayHi方法
func (h Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//Human实现Sing方法
func (h Human) Sing(lyrics string) {
	fmt.Println("La la la la...", lyrics)
}
//Employee重载Human的SayHi方法
func (e Employee) SayHi() {
	fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
	e.company, e.phone)
}
// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
	SayHi()
	Sing(lyrics string)
}
func main() {
	mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
	paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
	sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
	tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
	//定义Men类型的变量i
	var i Men
	//i能存储Student
	i = mike
	fmt.Println("This is Mike, a Student:")
	i.SayHi()
	i.Sing("November rain")
	//i也能存储Employee
	i = tom
	fmt.Println("This is tom, an Employee:")
	i.SayHi()
	i.Sing("Born to be wild")
	//定义了slice Men
	fmt.Println("Let's use a slice of Men and see what happens")
	x := make([]Men, 3)
	//这三个都是不同类型的元素,但是他们实现了interface同一个接口
	x[0], x[1], x[2] = paul, sam, mike
	for _, value := range x{
		value.SayHi()
	}
}

通过上面的代码,发现 interface 就是一组抽象方法的集合,它必须由其他非 interface 类型实现,而不能自我实现, Go通过 interface 实现了 duck-typing 即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。

空interface

interface ( interface{} )不包含任何的 method ,正因为如此,所有的类型都实现了 空interface空interface 对于描述起不到任何的作用(因为它不包含任何的 method ),但是 空interface 需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的 void* 类型。

// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s

一个函数把 interface{} 作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回 interface{} ,那么也就可以返回任意类型的值。

interface函数参数

interface 的变量可以持有任意实现该 interface 类型的对象,这给编写函数(包括 method )提供了一些额外的思考,是不是可以通过定义 interface 参数,让函数接受各种类型的参数。
举个例子:·fmt.Println· 是常用的一个函数,是否注意到它可以接受任意类型的数据。
打开fmt的源码文件,会看到这样一个定义:

type Stringer interface {
	String() string
}

也就是说,任何实现了 String 方法的类型都能作为参数被 fmt.Println 调用,来试一试:

package main
import (
	"fmt"
	"strconv"
)
type Human struct {
	name string
	age int
	phone string
}
// 通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {
	return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱"
}
func main() {
	Bob := Human{"Bob", 39, "000-7777-XXX"}
	fmt.Println("This Human is : ", Bob)
}

现在再回顾一下前面的 Box 示例,发现 Color 结构也定义了一个 method:String 。其实这也是实现了 fmt.Stringer 这个 interface ,即如果需要某个类型能被 fmt 包以特殊的格式输出,就必须实现 Stringer 这个接口。如果没有实现这个接口,fmt 将以默认的方式输出。

//实现同样的功能
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())

注:实现了 error 接口的对象(即实现了 Error() string 的对象),使用 fmt 输出时,会调用 Error() 方法,因此不必再定义String() 方法了。

变量存储的类型

interface 的变量里面可以存储任意类型的数值(该类型实现了 interface )。那么怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:

1.Comma-ok断言

Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T) ,这里 value 就是变量的值,ok 是一个 bool 类型,elementinterface 变量,T 是断言的类型。
如果 element 里面确实存储了 T 类型的数值,那么 ok 返回 true,否则返回 false
通过一个例子来更加深入的理解:

package main
import (
	"fmt"
	"strconv"
)

type Element interface{}
type List [] Element
type Person struct {
	name string
	age int
}
//定义了String方法,实现了fmt.Stringer
func (p Person) String() string {
	return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
	list := make(List, 3)
	list[0] = 1 // an int
	list[1] = "Hello" // a string
	list[2] = Person{"Dennis", 70}
	for index, element := range list {
		if value, ok := element.(int); ok {
			fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
		} else if value, ok := element.(string); ok {
			fmt.Printf("list[%d] is a string and its value is %s\n", index,
			value)
		} else if value, ok := element.(Person); ok {
			fmt.Printf("list[%d] is a Person and its value is %s\n", index,
			value)
		} else {
			fmt.Printf("list[%d] is of a different type\n", index)
		}
	}
}

是否注意到了多个 if 里面,if 里面允许初始化变量。断言的类型越多,那么 if else 也就越多,所以才引出了下面要介绍的 switch

2.switch测试

重写上面的这个实现:

package main

import (
	"fmt"
	"strconv"
)

type Element interface{}
type List []Element
type Person struct {
	name string
	age  int
}

// 打印
func (p Person) String() string {
	return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
}
func main() {
	list := make(List, 3)
	list[0] = 1       //an int
	list[1] = "Hello" //a string
	list[2] = Person{"Dennis", 70}
	for index, element := range list {
		switch value := element.(type) {
		case int:
			fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
		case string:
			fmt.Printf("list[%d] is a string and its value is %s\n", index,
				value)
		case Person:
			fmt.Printf("list[%d] is a Person and its value is %s\n", index,
				value)
		default:
			fmt.Println("list[%d] is of a different type", index)
		}
	}
}

这里有一点需要强调的是: element.(type) 语法不能在 switch 外的任何逻辑里面使用,如果要在
switch 外面判断一个类型就使用 comma-ok

嵌入interface

Go里面真正吸引人的是它内置的逻辑语法,就像在学习 Struct 时学习的匿名字段,那么相同的逻辑引入到 interface 里面,更加完美了。如果一个 interface1 作为 interface2 的一个嵌入字段,那么 interface2 隐式的包含了 interface1 里面的 method
可以看到源码包 container/heap 里面有这样的一个定义:

type Interface interface {
	sort.Interface //嵌入字段sort.Interface
	Push(x interface{}) //a Push method to push elements into the heap
	Pop() interface{} //a Pop elements that pops elements from the heap
}

看到 sort.Interface 其实就是嵌入字段,把 sort.Interface 的所有 method 给隐式的包含进来了。
也就是下面三个方法:

type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less returns whether the element with index i should sort
	// before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}

另一个例子就是 io 包下面的 io.ReadWriter ,它包含了 io 包下面的 ReaderWriter 两个 interface

// io.ReadWriter
type ReadWriter interface {
	Reader
	Writer
}
06-18 07:57