Merging other languages
This commit is contained in:
790
en/02.6.md
790
en/02.6.md
@@ -1,395 +1,395 @@
|
||||
# 2.6 interface
|
||||
|
||||
## interface
|
||||
Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计所折服。
|
||||
### 什么是interface
|
||||
简单的说,interface是一组method签名的组合,我们通过interface来定义对象的一组行为。
|
||||
|
||||
我们前面一章最后一个例子中Student和Employee都能SayHi,虽然他们的内部实现不一样,但是那不重要,重要的是他们都能`say hi`
|
||||
|
||||
让我们来继续做更多的扩展,Student和Employee实现另一个方法`Sing`,然后Student实现方法BorrowMoney而Employee实现SpendSalary。
|
||||
|
||||
这样Student实现了三个方法:SayHi、Sing、BorrowMoney;而Employee实现了SayHi、Sing、SpendSalary。
|
||||
|
||||
上面这些方法的组合称为interface(被对象Student和Employee实现)。例如Student和Employee都实现了interface:SayHi和Sing,也就是这两个对象是该interface类型。而Employee没有实现这个interface:SayHi、Sing和BorrowMoney,因为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 interface被Human、Student和Employee实现。同理,一个对象可以实现任意多个interface,例如上面的Student实现了Men和YoungChap两个interface。
|
||||
|
||||
最后,任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface。
|
||||
|
||||
### interface值
|
||||
那么interface里面到底能存什么值呢?如果我们定义了一个interface的变量,那么这个变量里面可以存实现这个interface的任意类型的对象。例如上面例子中,我们定义了一个Men interface类型的变量m,那么m里面可以存Human、Student或者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的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:
|
||||
|
||||
- Comma-ok断言
|
||||
|
||||
Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,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。
|
||||
- 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包下面的Reader和Writer两个interface:
|
||||
|
||||
// io.ReadWriter
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
|
||||
### 反射
|
||||
Go语言实现了反射,所谓反射就是能检查程序在运行时的状态。我们一般用到的包是reflect包。如何运用reflect包,官方的这篇文章详细的讲解了reflect包的实现原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html)
|
||||
|
||||
使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下:
|
||||
|
||||
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
|
||||
v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
|
||||
|
||||
转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如
|
||||
|
||||
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
|
||||
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
|
||||
|
||||
获取反射值能返回相应的类型和数值
|
||||
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
fmt.Println("type:", v.Type())
|
||||
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
|
||||
fmt.Println("value:", v.Float())
|
||||
|
||||
最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误
|
||||
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
v.SetFloat(7.1)
|
||||
|
||||
如果要修改相应的值,必须这样写
|
||||
|
||||
var x float64 = 3.4
|
||||
p := reflect.ValueOf(&x)
|
||||
v := p.Elem()
|
||||
v.SetFloat(7.1)
|
||||
|
||||
上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。
|
||||
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一章: [面向对象](<02.5.md>)
|
||||
* 下一节: [并发](<02.7.md>)
|
||||
# 2.6 Interface
|
||||
|
||||
## Interface
|
||||
|
||||
One of the subtlest design features in Go are interfaces. After reading this section, you will likely be impressed by their implementation.
|
||||
|
||||
### What is an interface
|
||||
|
||||
In short, an interface is a set of methods that we use to define a set of actions.
|
||||
|
||||
Like the examples in previous sections, both Student and Employee can `SayHi()`, but they don't do the same thing.
|
||||
|
||||
Let's do some more work. We'll add one more method `Sing()` to them, along with the `BorrowMoney()` method to Student and the `SpendSalary()` method to Employee.
|
||||
|
||||
Now, Student has three methods called `SayHi()`, `Sing()` and `BorrowMoney()`, and Employee has `SayHi()`, `Sing()` and `SpendSalary()`.
|
||||
|
||||
This combination of methods is called an interface and is implemented by both Student and Employee. So, Student and Employee implement the interface: `SayHi()` and `Sing()`. At the same time, Employee doesn't implement the interface: `SayHi()`, `Sing()`, `BorrowMoney()`, and Student doesn't implement the interface: `SayHi()`, `Sing()`, `SpendSalary()`. This is because Employee doesn't have the method `BorrowMoney()` and Student doesn't have the method `SpendSalary()`.
|
||||
|
||||
### Type of Interface
|
||||
|
||||
An interface defines a set of methods, so if a type implements all the methods we say that it implements the interface.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func (h *Human) Sing(lyrics string) {
|
||||
fmt.Println("La la, la la la, la la la la la...", lyrics)
|
||||
}
|
||||
|
||||
func (h *Human) Guzzle(beerStein string) {
|
||||
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
|
||||
}
|
||||
|
||||
// Employee overloads 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) //Yes you can split into 2 lines here.
|
||||
}
|
||||
|
||||
func (s *Student) BorrowMoney(amount float32) {
|
||||
s.loan += amount // (again and again and...)
|
||||
}
|
||||
|
||||
func (e *Employee) SpendSalary(amount float32) {
|
||||
e.money -= amount // More vodka please!!! Get me through the day!
|
||||
}
|
||||
|
||||
// define 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)
|
||||
}
|
||||
|
||||
We know that an interface can be implemented by any type, and one type can implement many interfaces simultaneously.
|
||||
|
||||
Note that any type implements the empty interface `interface{}` because it doesn't have any methods and all types have zero methods by default.
|
||||
|
||||
### Value of interface
|
||||
|
||||
So what kind of values can be put in the interface? If we define a variable as a type interface, any type that implements the interface can assigned to this variable.
|
||||
|
||||
Like the above example, if we define a variable "m" as interface Men, then any one of Student, Human or Employee can be assigned to "m". So we could have a slice of Men, and any type that implements interface Men can assign to this slice. Be aware however that the slice of interface doesn't have the same behavior as a slice of other types.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (h Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func (h Human) Sing(lyrics string) {
|
||||
fmt.Println("La la la la...", lyrics)
|
||||
}
|
||||
|
||||
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) //Yes you can split into 2 lines here.
|
||||
}
|
||||
|
||||
// Interface Men implemented by Human, Student and 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{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}
|
||||
|
||||
// define interface i
|
||||
var i Men
|
||||
|
||||
//i can store Student
|
||||
i = mike
|
||||
fmt.Println("This is Mike, a Student:")
|
||||
i.SayHi()
|
||||
i.Sing("November rain")
|
||||
|
||||
//i can store Employee
|
||||
i = Tom
|
||||
fmt.Println("This is Tom, an Employee:")
|
||||
i.SayHi()
|
||||
i.Sing("Born to be wild")
|
||||
|
||||
// slice of Men
|
||||
fmt.Println("Let's use a slice of Men and see what happens")
|
||||
x := make([]Men, 3)
|
||||
// these three elements are different types but they all implemented interface Men
|
||||
x[0], x[1], x[2] = paul, sam, mike
|
||||
|
||||
for _, value := range x {
|
||||
value.SayHi()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
An interface is a set of abstract methods, and can be implemented by non-interface types. It cannot therefore implement itself.
|
||||
|
||||
### Empty interface
|
||||
|
||||
An empty interface is an interface that doesn't contain any methods, so all types implement an empty interface. This fact is very useful when we want to store all types at some point, and is similar to void* in C.
|
||||
|
||||
// define a as empty interface
|
||||
var a interface{}
|
||||
var i int = 5
|
||||
s := "Hello world"
|
||||
// a can store value of any type
|
||||
a = i
|
||||
a = s
|
||||
|
||||
If a function uses an empty interface as its argument type, it can accept any type; if a function uses empty as its return value type, it can return any type.
|
||||
|
||||
### Method arguments of an interface
|
||||
|
||||
Any variable can be used in an interface. So how can we use this feature to pass any type of variable to a function?
|
||||
|
||||
For example we use fmt.Println a lot, but have you ever noticed that it can accept any type of argument? Looking at the open source code of fmt, we see the following definition.
|
||||
|
||||
type Stringer interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
This means any type that implements interface Stringer can be passed to fmt.Println as an argument. Let's prove it.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
// Human implemented fmt.Stringer
|
||||
func (h Human) String() string {
|
||||
return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone
|
||||
}
|
||||
|
||||
func main() {
|
||||
Bob := Human{"Bob", 39, "000-7777-XXX"}
|
||||
fmt.Println("This Human is : ", Bob)
|
||||
}
|
||||
|
||||
|
||||
Looking back to the example of Box, you will find that Color implements interface Stringer as well, so we are able to customize the print format. If we don't implement this interface, fmt.Println prints the type with its default format.
|
||||
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor())
|
||||
|
||||
Attention: If the type implemented the interface `error`, fmt will call `error()`, so you don't have to implement Stringer at this point.
|
||||
|
||||
### Type of variable in an interface
|
||||
|
||||
If a variable is the type that implements an interface, we know that any other type that implements the same interface can be assigned to this variable. The question is how can we know the specific type stored in the interface. There are two ways which I will show you.
|
||||
|
||||
- Assertion of Comma-ok pattern
|
||||
|
||||
Go has the syntax `value, ok := element.(T)`. This checks to see if the variable is the type that we expect, where "value" is the value of the variable, "ok" is a variable of boolean type, "element" is the interface variable and the T is the type of assertion.
|
||||
|
||||
If the element is the type that we expect, ok will be true, false otherwise.
|
||||
|
||||
Let's use an example to see more clearly.
|
||||
|
||||
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 {
|
||||
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.Println("list[%d] is of a different type", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It's quite easy to use this pattern, but if we have many types to test, we'd better use `switch`.
|
||||
|
||||
- switch test
|
||||
|
||||
Let's use `switch` to rewrite the above example.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
One thing you should remember is that `element.(type)` cannot be used outside of the `switch` body, which means in that case you have to use the `comma-ok` pattern .
|
||||
|
||||
### Embedded interfaces
|
||||
|
||||
The most beautiful thing is that Go has a lot of built-in logic syntax, such as anonymous fields in struct. Not suprisingly, we can use interfaces as anonymous fields as well, but we call them `Embedded interfaces`. Here, we follow the same rules as anonymous fields. More specifically, if an interface has another interface embedded within it, it will have as if it has all the methods that the embedded interface has.
|
||||
|
||||
We can see that the source file in `container/heap` has the following definition:
|
||||
|
||||
type Interface interface {
|
||||
sort.Interface // embedded 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
|
||||
}
|
||||
|
||||
We see that `sort.Interface` is an embedded interface, so the above Interface has the three methods contained within the `sort.Interface` implicitly.
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Another example is the `io.ReadWriter` in package `io`.
|
||||
|
||||
// io.ReadWriter
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
|
||||
### Reflection
|
||||
|
||||
Reflection in Go is used for determining information at runtime. We use the `reflect` package, and this official [article](http://golang.org/doc/articles/laws_of_reflection.html) explains how reflect works in Go.
|
||||
|
||||
There are three steps involved when using reflect. First, we need to convert an interface to reflect types (reflect.Type or reflect.Value, this depends on the situation).
|
||||
|
||||
t := reflect.TypeOf(i) // get meta-data in type i, and use t to get all elements
|
||||
v := reflect.ValueOf(i) // get actual value in type i, and use v to change its value
|
||||
|
||||
After that, we can convert the reflected types to get the values that we need.
|
||||
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
fmt.Println("type:", v.Type())
|
||||
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
|
||||
fmt.Println("value:", v.Float())
|
||||
|
||||
Finally, if we want to change the values of the reflected types, we need to make it modifiable. As discussed earlier, there is a difference between pass by value and pass by reference. The following code will not compile.
|
||||
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
v.SetFloat(7.1)
|
||||
|
||||
Instead, we must use the following code to change the values from reflect types.
|
||||
|
||||
var x float64 = 3.4
|
||||
p := reflect.ValueOf(&x)
|
||||
v := p.Elem()
|
||||
v.SetFloat(7.1)
|
||||
|
||||
We have just discussed the basics of reflection, however you must practice more in order to understand more.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Object-oriented](02.5.md)
|
||||
- Next section: [Concurrency](02.7.md)
|
||||
|
||||
Reference in New Issue
Block a user