设为首页 加入收藏

TOP

go基础-接口(一)
2023-08-06 07:49:42 】 浏览:136
Tags:基础 接口

一、概述

接口是面向对象编程的重要概念,接口是对行为的抽象和概括,在主流面向对象语言Java、C++,接口和类之间有明确关系,称为“实现接口”。这种关系一般会以“类派生图”的方式进行,经常可以看到大型软件极为复杂的派生树,随着系统的功能不断增加,这棵“派生树”会变得越来越复杂。

Go语言接口模型非常特别,就目前观察是独创。go接口设计是非侵入式,只要类型方法是接口方法的超集,那么就认为类型实现了接口,两者之间不需要显示关联,当然也没有implements关键字,称为隐式实现。相比Java、C++主流面向对象语言需要显示实现接口,go的方式更加灵活、松散、耦合更低,当然也更加隐晦、代码可读性降低(当然有人不同意这种看法)。在不修改类型定义情况下,可以为其添加接口,这在Java、C++下是不可思议的。go的接口满足鸭子模型,所谓鸭子类型:只要走起路来像鸭子、叫起来也像鸭子,那么就可以把它当作鸭子。

 

二、基本使用

接口定义,描述一堆方法的集合,给出方法声明即可,不能有默认实现,也不能有变量

type User interface {
    Say()
    GetName() string
}

定了一个user接口,包含两个方法

 

任何类型都可以实现这两个方法,不需要显示使用implements关键字。满足两个条件,与接口方法签名完全一致,是接口方法的超集。即可判定类型实现了接口。

type Sales struct {                    // 定义类型
    name string
}

func (p *Sales) GetName() string {    // 接口方法一
    return p.name
}

func (p *Sales) Say() {                // 接口方法二
    fmt.Println("Hi, I'm", p.name)
}

func (p *Sales) peddle() {            
    fmt.Printf("%s peddle", p.name)
}

Sales类型满足两个条件,可判断实现了User接口。从代码上看两者没有直接关联,这就是隐式实现。

 

按照上面的两个条件,Sales也实现了如下接口

type Person interface {
    GetName() string
}

可以看到非常松散,就是这么简洁。再次强调只要满足两个条件:与接口方法签名一致,是接口方法的超集,即可判定类型实现了接口。从类型自身角度看,完全不知道自己实现了哪些接口。

 

通过实例调用方法

var sales Sales = Sales{name: "tom"}
sales.Say()

 

通过接口调用方法,只要类型实现了接口,就可以赋值给接口变量,并使用接口调用方法

var user User = &Sales{name: "tom"}        // 赋值给接口变量,注意是地址
user.Say()                                 // 通过接口调用方法

fmt.Printf("%T\n", user)                  // *main.Sales

接口是引用类型,和指针一样,只能指向实例的地址。

 

接口主要目标是解耦,通常称为面向接口编程,主流使用方式是函数形参是接口类型,调用时候传递接口变量,这也是接口存在的意义。

func PrintName(user User) {                    // 形参是User接口类型
    fmt.Println("姓名:", user.GetName())
}

var sales User = &Sales{name: "tom"}        
PrintName(sales)                            // 传入user接口变量

形参是接口类型,可传入所有实现了该接口的实例,不在依赖具体类型。

 

在标准库中大量使用接口。比如排序是普片需求,标准库提供了排序函数,形参是接口类型,任何实现了该接口的类型,都可直接使用排序函数

type Interface interface {        // 排序接口
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

func Sort(data Interface) {        // 标准库排序函数
    ...
}

 

和结构体一样,接口也支持继承特性

type User interface {
    Say()
    GetName() string
}

type Admin interface {
    User                // 继承User接口
    TeamName string        // 自有属性
}

需要实现包括继承的所有方法,才判定实现了该接口

 

并非只能使用结构体实现接口,其他自定义类型也可以实现接口,如下X类型实现了Plus接口

type Plus interface {
    incr()
}

type X int

func (x *X) incr() {
    *x += 1
    fmt.Println(*x)
}

 

三、接口断言

在接口变量上操作,用于检查接口类型变量是否实现了期望的接口或者具体的类型。使用接口的本质,就是实例类型和接口类型之间转换,而是否允许转换就依赖断言

value, ok := x.(T)

x 表示接口的类型,T 表示具体类型(也可以是接口),可根据该布尔值判断 x 是否为 T 类型。

  • 如果 T 是实例类型,类型断言会检查 x 的动态类型是否满足 T。如果成功返回 x 的动态值,其类型是 T。
  • 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果成功返回 值是 T 的接口值。
  • 无论 T 是什么类型,如果 x 是 nil 接口值,类型断言都会失败。

 

使用上面案例进行断言

var user User = &Sales{name: "tony"}

if value, ok := user.(User); ok {        // true,  接口断言
    value.Say()    
}

_, ok = user.(*Sales)                    // true, 具体类型断言, 注意这里使用了指针类型

 

注意,如果不接收第二个返回值(也就是 ok),断言失败时会 panic,对nil断言同样也会 panic。

admin := user.(Admin)    // Admin是管理员接口,断言失败panic

 

具体类型实例如何断言,可以先转为为接口,然后再进行断言

user1 := Sales{"tom"}

var data interface{} = user1      // 转换为空接口

if _, ok := data.(Sales); ok {    // 再进行断言
    fmt.Println("yes")
}

 

断言常见使用场景,异常捕获时判定错误类型

func ProtectRun(entry func()) {
    defer func() {
        err := recover()            // 获取错误类型
        
        switch err.(type) {            // 断言错误类型, 不同类型的错误采取不同的处理方式
        case runtime.Error: 
            fmt.Println("runtime error:", err)
        default:
            fmt.Println("error:", err)
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇go基础-方法 下一篇Go 介绍

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目