Go语言里面设计最精妙的应该算interface,它让面对对象,内容组织实现的非常方便,接下来将简要记录一下interface的使用:
什么是interface?简单的说,interface是一组method签名的组合,我们通过interface定义对象的一组行为
接口定义
我们可以这样定义一个接口:
type Listener interface{
print_info()
}
接口有这样几个特点:
- 接口命名习惯以
er
结尾 - 接口只有方法声明,没有实现,没有数据字段
- 接口可以匿名嵌入到其他接口,或嵌入到结构中
下面,我们来使用一下接口:
package main
import "fmt"
type Listener interface{
print_info()
}
type Student struct{
name string
age int
}
func (s Student) print_info() {
fmt.Println("Student的print_info")
}
type People struct{
name string
age int
}
func (p *People) print_info(){
fmt.Println("People的print_info")
}
type MyStr string
func (s *MyStr) print_info() {
fmt.Println("MyStr的print_info")
}
func main(){
var l Listener
l = Student{"laozhang", 18}
l.print_info()
l = &People{"laowang", 20}
l.print_info()
var s MyStr = "laohei"
l = &s
l.print_info()
}
// 打印结果如下
Student的print_info
People的print_info
MyStr的print_info
值得注意的是,上面这个栗子,我们定义了一个接口类型,并且分别在Student
、People
以及MyStr
中实现该接口的方法,如果与此接口方法绑定的是一个指定变量,那么在使用此接口方法时,传递给接口变量也应该是指针。这一点与普通的方法有所不同,并不会自动进行转换!
上面栗子中,在接口里定义的是一个无返回值的方法;下面,我们来定义一个有返回值的情况,如下:
package main
import (
"fmt"
"strconv"
)
type Listener interface{
get_info() string
}
type Student struct{
name string
age int
}
func (s Student) get_info() string {
return "name: " + s.name + " age: " + strconv.Itoa(s.age)
}
func main() {
s := Student{"laozhang", 18}
var l Listener = s
info := l.get_info()
fmt.Println(info)
}
// 打印结果如下
name: laozhang age: 18
多态
多态,简单来说,就是调用同一个方法却有着不同的表现。下面对上面栗子进行修改一下实现一个多态行为:
package main
import "fmt"
type Listener interface{
print_info()
}
type Student struct{
name string
age int
}
func (s Student) print_info() {
fmt.Println("Student的print_info")
}
type People struct{
name string
age int
}
func (p *People) print_info(){
fmt.Println("People的print_info")
}
type MyStr string
func (s *MyStr) print_info() {
fmt.Println("MyStr的print_info")
}
func who_print_info(listener Listener){
listener.print_info()
}
func main(){
list := make([]Listener, 3)
list[0] = Student{"laozhang", 18}
list[1] = &People{"laowang", 20}
var s MyStr = "laohei"
list[2] = &s
for _, data := range list{
who_print_info(data)
}
}
// 打印结果如下
Student的print_info
People的print_info
MyStr的print_info
继承
interface跟struct一样,都拥有继承的功能。我们可以定义两个interface,使其中一个interface以匿名字段的形式存在于另一个interface当中,以达到继承的效果,如下:
package main
import "fmt"
type Listener interface{
print_info()
}
type ChildListener interface{
Listener
print_child()
}
type Student struct{
name string
age int
}
func (s Student) print_info() {
fmt.Println("Student的print_info")
}
func (s Student) print_child() {
fmt.Println("Student的print_child")
}
func main(){
var cl ChildListener = Student{"laozhang", 18}
cl.print_info()
cl.print_child()
}
// 打印结果如下
Student的print_info
Student的print_child
转换
转换,我们只需要明白一个概念,超集可以转换为子集,而子集不可以转换为超集合。有两个interface,其中一个interface中拥有另一个interface的匿名字段时,我们称这个为超级,那么另一个则为子集。举个栗子:
type Listener interface{
print_info()
}
type MyListener interface{
Listener
print_other()
}
此时,MyListener
就是超级,Listener
就为子集。我们可以这样进行转换:
var l Listener
var ml MyListener
l = ml
但是,如果子集转换超集,那么就会发生编译错误,下面这种转换方式是错误的:
var l Listener
var ml MyListener
ml = l
空接口
空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。它有点类似于C语言的Void *类型,例如:
var v1 interface{} = 1
var v2 interface{} = 'A'
var v3 interface{} = "abc"
var v4 interface{} = 3.12
var v5 interface{} = struct{ x int }{1}
var v6 interface{} = &struct{ x int }{1}
当函数可以接收任何类型参数时,我们可以将其定义为interface{}
,最典型的就是标准库fmt中的各种打印函数接收的参数,如下:
fmt.Print(a ...interface{})
fmt.Println(a ...interface{})
fmt.Printf(format string, a ...interface{})
我们知道空接口可以存储任意类型的数据,那么我们如何知道这个变量里面实际存储的是什么类型的对象呢?目前常用的方法有两种:
- Comma-ok
Go语言里有一种语法,可以直接判断该变量是否为该类型的变量:value, ok = element.(T)
,这里value就是变量的值,ok是一个bool值,element就是我们的空接口类型,T为我们需要判断是否为T类型。
如果element确实存储了T类型的值,那么ok会返回true,否则返回false。举个栗子:
var v interface{} = "abc"
value, ok := v.(int)
value2, ok2 := v.(string)
fmt.Println(value, ok)
fmt.Println(value2, ok2)
// 打印结果如下
0 false
abc true
如果element未存储了T类型的值,那么value会返回T类型的零值!
- switch-case
var v interface{} = "abc"
switch value := v.(type) {
case int:
fmt.Printf("int类型, value = %v", value)
case string:
fmt.Printf("string类型, value = %v", value)
default:
fmt.Printf("其他类型, value = %v", value)
}
// 打印结果如下
string类型, value = abc
值得注意的是,element.(type)
不能在switch以外的任何逻辑里面使用
至此,Over~~~