接口

本节学习目标

  • 理解什么是接口?
  • 接口的实际用途?
  • 空接口
  • 类型断言的使用
  • 类型选择的使用
  • 指针接受者和值接受者
  • 实现多个接口
  • 接口的嵌套
  • 接口的零值

理解什么是接口?

在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。

在 Go 语言中,接口就是方法签名(Method Signature)的集合。当一个类型定义了接口中的所有方法,我们称它实现了该接口。这与面向对象编程(OOP)的说法很类似。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法

看一个完整的接口使用列子

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 接口

接口的实际用途?

我们有这样一个需求,如果要计算一个公司员工的工资总和,前提公司的员工目前有两种类型,普通职员,销售员(基本工资+ 提成),可以以后还要扩展到财务,经理等等,怎么设计比较合理?

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{}。由于空接口没有方法,因此所有类型都实现了空接口

package main

import (  
    "fmt"
)

func describe(i interface{}) {  
    fmt.Printf("Type = %T, value = %v\n", 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

如下

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 指定的是值

上代码

package main

import (  
    "fmt"
)

func findType(i interface{}) {  
    switch i.(type) {
    case string:
        fmt.Printf("I am a string and my value is %s\n", i.(string))
    case int:
        fmt.Printf("I am an int and my value is %d\n", i.(int))
    default:
        fmt.Printf("Unknown type\n")
    }
}
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 方法


值接受者和指针接受者

下面我们看一下指针接受者有什么不同之处

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("小霸")的简写方式,当然你也可以使用第二种方式

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


实现多个接口

类型如何实现多个接口? 只要这个类型 实现了 多个接口里面的方法即可实现多个类型

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 不能使用在类型转换中。


接口的嵌套

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

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 %v\n", d1, d1)
    }
}

d1 is nil and has type <nil> value <nil>

对零值,调用接口方法,会差生panic

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券