前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

接口

作者头像
酷走天涯
发布2019-06-11 16:42:30
5190
发布2019-06-11 16:42:30
举报

本节学习目标

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

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

在 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 = %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,该语法用于获得接口的底层值。

代码语言: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

注意第二种方式,一旦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 %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 匹配成功,会打印出相应的语句

  • 还可以将一个类型和接口相比较。如果一个类型实现了接口,那么该类型与其实现的接口就可以互相比较
代码语言:javascript
复制
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 %v\n", d1, d1)
    }
}

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

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.12.24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 理解什么是接口?
  • 接口的实际用途?
  • 空接口
  • 类型断言的使用
  • 类型选择
  • 值接受者和指针接受者
  • 实现多个接口
  • 接口的嵌套
  • 接口的零值
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档