本节学习目标
- 理解什么是接口?
- 接口的实际用途?
- 空接口
- 类型断言的使用
- 类型选择的使用
- 指针接受者和值接受者
- 实现多个接口
- 接口的嵌套
- 接口的零值
理解什么是接口?
在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。
在 Go 语言中,接口就是方法签名(Method Signature)的集合。当一个类型定义了接口中的所有方法,我们称它实现了该接口。这与面向对象编程(OOP)的说法很类似。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法
看一个完整的接口使用列子
代码语言:javascript复制package main
import "fmt"
// 1.接口的定义
type Handle interface{
UpdateName(name string)
}
type Dog struct{
name string
}
// 2.实现接口的方法
func (dog *Dog)UpdateName(name string){
dog.name = name
}
// 3.定义一个方法 把接口当做参数传递
func UpdateName(handle Handle,name string){
handle.UpdateName(name)
}
func main(){
dog := Dog{name:"小黄"}
UpdateName(&dog,"小白狗")
fmt.Println(dog)
}
1.我们定了一个接口 handle 接口,里面有有一个updateName 方法,功能就改名 2.我们定义了Dog 结构体 我们赋予这个结构体一个改名的功能方法,即updateName,则表示这个结构体具有改名的能力 3.我们定义了方法,专门实现了改名结构的对象改名,注意我们的参数是接口类型handle,第二参数是新名字 4.UpdateName(&dog,"小白狗") 因为 *Dog 实现了接口类型的方法,所以我们就可以把它当做参数,进行传递
注意接口的实现过程
- 如果一个类型包含了接口中声明的所有方法,那么它就隐式地实现了 Go 接口
接口的实际用途?
我们有这样一个需求,如果要计算一个公司员工的工资总和,前提公司的员工目前有两种类型,普通职员,销售员(基本工资 提成),可以以后还要扩展到财务,经理等等,怎么设计比较合理?
代码语言:javascript复制package main
import "fmt"
type Salesman struct{
id int
base float64
pf float64
}
type Engineer struct {
id int
base float64
}
// 定义一个计算公司的接口
type SalaryCalculator interface {
CalculateSalary() float64
}
// 实现接口
func (e Engineer)CalculateSalary()float64{
return e.base
}
func (s Salesman)CalculateSalary()float64{
return s.base s.pf
}
func calculateTotalExpense(list []SalaryCalculator)float64{
total := 0.0
for _,v := range list{
total = v.CalculateSalary()
}
return total
}
func main(){
enginner := Engineer{id:1,base:2000}
salesman := Salesman{id:2,base:1000,pf:10000}
persons := []SalaryCalculator{enginner}
persons = append(persons,salesman)
total := calculateTotalExpense(persons)
fmt.Println(total)
}
image.png
我们定义了专门用来输入员工公司的接口方法,每种类型的员工只要实现了这个方法,那么它就实现了这个接口,不管以后,有什么新类型的员工加入,不需要更改整个计算的业务逻辑,只需要为其实现对应的接口即可
空接口
没有包含方法的接口称为空接口。空接口表示为 interface{}。由于空接口没有方法,因此所有类型都实现了空接口
代码语言:javascript复制package main
import (
"fmt"
)
func describe(i interface{}) {
fmt.Printf("Type = %T, value = %vn", i, i)
}
func main() {
s := "Hello World"
describe(s)
i := 55
describe(i)
strt := struct {
name string
}{
name: "Naveen R",
}
describe(strt)
describe(nil)
}
image.png
describe 方法的参数是i 是一个空接口类型,由于空接口 没有任何方法,默认所有类型都实现了它,所以它能够接受任何类型
类型断言的使用
i.(T)
接口 i 的具体类型是 T,该语法用于获得接口的底层值。
package main
import (
"fmt"
)
func assert(i interface{}) {
// 第一种写法
s,ok := i.(int) //get the underlying int value from i
if(ok){
fmt.Println(s)
}
// 第二种写法
s1 := i.(int) //get the underlying int value from i
fmt.Println(s1)
}
func main() {
var s interface{} = 56
assert(s)
}
image.png
注意第二种方式,一旦s的值不是int 类型,那么go 就会抛出一个panic
如下
代码语言:javascript复制package main
import (
"fmt"
)
func assert(i interface{}) {
// 第一种写法
s,ok := i.(int) //get the underlying int value from i
if(ok){
fmt.Println(s)
}
// 第二种写法
s1 := i.(int) //get the underlying int value from i
fmt.Println(s1)
}
func main() {
var s interface{} = "56"
assert(s)
}
image.png
类型选择
类型选择用于将接口的具体类型与很多 case 语句所指定的类型进行比较。它与一般的 switch 语句类似。唯一的区别在于类型选择指定的是类型,而一般的 switch 指定的是值
上代码
代码语言:javascript复制package main
import (
"fmt"
)
func findType(i interface{}) {
switch i.(type) {
case string:
fmt.Printf("I am a string and my value is %sn", i.(string))
case int:
fmt.Printf("I am an int and my value is %dn", i.(int))
default:
fmt.Printf("Unknown typen")
}
}
func main() {
findType("Naveen")
findType(77)
findType(89.98)
}
switch i.(type) 表示一个类型选择。每个 case 语句都把 i 的具体类型和一个指定类型进行了比较。如果 case 匹配成功,会打印出相应的语句
- 还可以将一个类型和接口相比较。如果一个类型实现了接口,那么该类型与其实现的接口就可以互相比较
package main
import "fmt"
type Action interface {
Eat()
}
type Dog struct {
name string
}
func (d Dog) Eat(){
fmt.Printf("%s,吃了东西",d.name)
}
func descrbe(i interface{}){
switch v:=i.(type) {
case Action:
v.Eat();break;
default:
fmt.Println("没有实现eat接口")
break
}
}
func main() {
dog := Dog{name:"小花狗"}
descrbe(dog)
}
v:=i.(type) v 如果实现了Action接口 就会调用 v.Eat 方法
值接受者和指针接受者
下面我们看一下指针接受者有什么不同之处
代码语言:javascript复制package main
import "fmt"
type Action interface {
modifyName(name string)
}
type Dog struct {
name string
}
func (d *Dog) modifyName(name string){
d.name = name
}
func main() {
dog := Dog{name:"小花狗"}
var action = dog
action.modifyName("小霸")
fmt.Println(dog)
fmt.Println(action)
}
这个一个有坑的例子,var action = dog 这个是一个赋值运算,由于结构体是值类型 action的和dog 不是同一个内存地址,所以action.modifyName 不会修改dog的名字,日志输入如下
image.png
注意 action.modifyName("小霸"),其实是(&action).modifyName("小霸")的简写方式,当然你也可以使用第二种方式
代码语言:javascript复制package main
import "fmt"
type Action interface {
modifyName(name string)
}
type Dog struct {
name string
}
func (d *Dog) modifyName(name string){
d.name = name
}
func main() {
dog := Dog{name:"小花狗"}
var action Action = &dog
action.modifyName("小霸")
fmt.Println(dog)
fmt.Println(action)
}
var action Action = &dog 由于 *dog 实现了接口 action 所以可以将&dog 转换为接口类型
image.png
实现多个接口
类型如何实现多个接口? 只要这个类型 实现了 多个接口里面的方法即可实现多个类型
代码语言:javascript复制package main
import "fmt"
type Action interface {
Eat(name string )
}
type Update interface{
UpdateName(name string )
}
type Dog struct {
name string
}
func (d *Dog)Eat(food string){
fmt.Printf("%s,吃了 %s",d.name,food)
}
func (d *Dog)Update(name string){
fmt.Printf("%s,改名为: %s",d.name,name)
d.name = name
}
func describe(i interface{}){
switch v:= i.(type) {
case Update:
v.UpdateName("小花")
case Action:
v.Eat("骨头")
default:
fmt.Println("没有实现接口")
}
}
func main() {
dog := Dog{name:"小花狗"}
describe(dog)
describe(&dog)
}
image.png
注意
1.dog 没有实现UpdateName 和 Eat 的方法 而是 *dog 实现了这个方法 2.describe(&dog) 为什么case Action 子条件满足,而不执行case v.Eat("骨头")的方法呢?因为go会为每个case 子句后面默认执行break操作。那么怎么让代码执行下去呢?Fallthrough 不能使用在类型转换中。
接口的嵌套
代码语言:javascript复制package main
import "fmt"
type Update interface{
UpdateName(name string )
}
// 嵌套接口的用法
type Action interface {
Update
Eat(name string )
}
type Dog struct {
name string
}
func (d Dog)Eat(food string){
fmt.Printf("%s,吃了%s",d.name,food)
}
func (d Dog)UpdateName(name string){
}
func main() {
var action Action = Dog{name:"狗狗"}
fmt.Println(action)
}
Action 嵌套了接口Update 类型需要实现Action 里面的方法 和 嵌套接口 Update里面的方法,才能说明,类型实现了接口Action
接口的零值
接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil
代码语言:javascript复制package main
import "fmt"
type Describer interface {
Describe()
}
func main() {
var d1 Describer
if d1 == nil {
fmt.Printf("d1 is nil and has type %T value %vn", d1, d1)
}
}
d1 is nil and has type <nil> value <nil>
对零值,调用接口方法,会差生panic