Go中接口的设计与实现

Go语言中的接口采用的是隐式实现,不需要去申明实现,只需要直接实现接口所定义的全部方法即可,同时区分了直接实现与指针实现两种形态,在实际使用时需要注意和关注

所属分类 Golang

相关标签 接口实现断言

接口

接口是面向对象编程实现多态的基操。

在绝大多数情况下,数据可能是不同类型,不同类型却可能存在着一个和多个共通点。

这些共通点就是抽象的基础。

接口的本质就是某一个抽象的共通点,实际类型可能是一对多(一个类型实现多个接口)或多对一(多种类型实现一个接口),这就是多态的体现。

举个例子:

  • 老王和老张两个好兄弟是两个具体的类型,我们提取他们身上可能存在的共通点。
  • 他们都是人,那么在编程世界中,人是可以作为一个接口存在的。
  • 人都需要睡觉,所以睡觉可以作为一个接口方法存在。
  • 老王喜欢趴着睡,就相当于老王的睡觉方法的具体操作是趴着,相当于接口方法的实现。
  • 老张喜欢侧着睡,就相当于老张的睡觉方法的具体操作是侧着,相当于接口方法的实现。

从编程的角度来看,如果调取人这个接口让其执行睡觉方法,最终结果就是老王趴着睡、老张侧着睡。

所以,接口是什么?

接口是一组包含方法名(入参、返回值)的未进行具体实现的方法集。

判断一个类型是否实现了接口,就看该类型是否全部实现了接口所定义的方法。

Go接口特点

Go 语言中接口的独特之处在于隐式实现。

具体的类型并不需要声明其实现了哪些接口,实现接口必须的方法即可完成接口的实现。

我们先看看 Java 语言中如何实现一个接口:

// 接口
public interface People {
    boolean Sleep();
}
// 实现(类需要显式申明实现接口)
public class PeopleImpl implements People {
    @Override
    public boolean Sleep() {
        System.out.println("睡了!");
        return true;
    }
}

这里有一个关键字 implements,用来声明实现某接口。

Java 中是类实现接口而不是具体类型。

在 Go 中,不需要声明实现,只需要实现了接口的全部方法,便隐式的实现了接口。

依旧上面那段逻辑,修改成 Go 语言形式去实现。

// 接口
type People interface {
    Sleep() bool
}

// 具体类型
type Man struct {
    Name string
}

// 类型实现接口方法
func (man Man) Sleep() bool {
    fmt.Println("我是" + man.Name + "!我睡了!")
    return true
}

上文我们提到,在 Go 中只要实现了接口的全部方法就隐式的实现了接口。

上述代码中,People 接口只有一个 Sleep() 方法,Man 类型实现了该方法,那 Man 类型是否实现了接口 People 呢?

func main() {
    // 定义一个接口对象
    var p People
    // 定义个类型对象
    lw := Man{"老王"}
    // 将类型赋给接口
    p = lw
    // 调用接口方法
    p.Sleep()
}

输出结果如下:

我是老王!我睡了!

Go 中接口实现并不仅仅是类型直接实现方法,还有指针实现形式。

指针实现的方法尽可以通过指针调用,比如下方代码:

// 具体类型
type Women struct {
    Name string
}

// 类型实现接口方法
func (women *Women) Sleep() bool {
    fmt.Println("我是" + women.Name + "!我睡了!")
    return true
}

func main() {
    // 定义一个接口对象
    var p People
    // 定义个类型对象
    lw := Women{"王阿姨"}
    // 将类型赋给接口
    // 编译报错:Cannot use 'lw' (type Women) as type People Type does not implement 'People' as 'Sleep' method has a pointer receiver 
    // 提示您 Women 类型并没有实现 People 接口
    p = lw
     // 将指针赋给接口对象,才能正确实现接口
    p = &lw
    // 调用接口方法
    p.Sleep()
}

可以通过 x.(T) 的方式验证是否已实现,x 只适用于 interface 类型。

// 检查实现
func checkPeople(p interface{}) bool {
    if _, ok := p.(People); ok {
        return true
    }
    return false
}

func main() {
    // 定义个类型对象
    lw := Man{"老王"}
    // 判断检查结果
    if checkPeople(lw) {
        fmt.Println(lw.Name + "是个人!")
    } else {
        fmt.Println(lw.Name + "不是人!")
    }
}

输出结果如下:

老王是个人!

Go空接口

Go 中是可以定义空接口的,比如上文中 checkPeople 方法的入参就是空接口。

因为空接口中不包含任何方法,就意味着任何类型都实现了空接口,所以可以利用空接口存储任意类型的数据。

func main() {
    i := 1
    s := "string"
    var o interface{}
    o = i
    fmt.Printf("O值:%v
", o)
    o = s
    fmt.Printf("O值:%v
", o)
}

输出结果如下:

O值:1
O值:string

既然空接口可以接受任意类型的数据,那自然会涉及到如何区分类型的需要。

目前有两种方式去区分:断言、判断。

相当于对上方 x.(T) 的多重判断。

func checkDataType(p interface{}) {
    // 类型强转
    if _,ok := p.(People); ok {
        fmt.Println("TA是个人!")
    } else if _,ok := p.(int); ok {
        fmt.Println("TA是个数字!")
    } else if _,ok := p.(string); ok {
        fmt.Println("TA是个字符串儿!")
    }
}

将语法改成 switch 本质没什么区别。

func checkDataType(p interface{}) {
    // 获取类型
    switch v := p.(type) {
    case People:
        fmt.Printf("TA是个人!%v
", v)
    case int:
        fmt.Printf("TA是个数字!%v
", v)
    case string:
        fmt.Printf("TA是个字符串儿!%v
", v)
    }
}

嵌套接口

嵌套接口有点类似于继承的感觉,当方法很多的时候,我们新建的接口不一定需要写出所有的方法。

type People interface {
    Sleep() bool
}

type WorkMan interface {
    GoWork() int
}

// 该接口直接嵌套其他接口形成新的接口
// 表示该接口具备所嵌入接口的全部方法
type Technician interface {
    People
    WorkMan
}

米虫

做一个有理想的米虫,伪全栈程序猿,乐观主义者,坚信一切都是最好的安排!

本站由个人原创、收集或整理,如涉及侵权请联系删除

本站内容支持转发,希望贵方携带转载信息和原文链接

本站具有时效性,不提供有效、可用和准确等相关保证

本站不提供免费技术支持,暂不推荐您使用案例商业化

发表观点

提示

昵称

邮箱

QQ

网址

当前还没有观点发布,欢迎您留下足迹!

同类其他

Golang

Go语言 & 与 * 取值赋值以及函数入参的区别

在 Go 中函数可以接受值传递和指针传递,使用时就涉及到 & 内存地址(指针)与 * 指针赋值的使用,它们的区别是什么?在实际业务使用中,值传递和指针传递的分别应对什么场景需要?针对使用时机进行分析。

Go语言中Struct增强Tag基本应用

Go 语言如何将 json 键名转为小写开头? struct 中经常用反引号包括起来的字符串,这便是 tag,一般由键值对形式存在,主要用于增强结构体的使用,可用作为判断标识进行补充处理

Go数据类型rune介绍和使用

Go 中比较常见的 int、string、bool、float 基本数据类型之外还有其他的数据类型可以应用在特殊场景,比如 rune 就是类似于 int32,因为其可表示的字符范围更大,实际工作中可以用来计算字符串的真实长度

GOPROXY依赖包代理设置

Go1.11版本开始支持包依赖管理工具,新增了GOPROXY环境变量,用于配置依赖包下载代理,通过代理配置可以实现翻墙下载一些所需的依赖包,可以说相当实用

GoLand设置gofmt和goimports代码格式化

GoLand 在保存代码时,可以自动调用 gofmt 和 goimports 实现自动格式化代码,在新版本中可以通过 File Watchers 插件来完成这些配置,配置位置位于File

Go报错xx is shadowed during return

调试 Go 程序报错 xx is shadowed during return,方法在返回的时候不是预期的返回结果,错误的产生应当是在相同作用域中出现了同名的变量导致,根据实际业务场景进行修改

选择个人头像

昵称

邮箱

QQ

网址

评论提示

  • 头像:系统为您提供了12个头像自由选择,初次打开随机为你选择一个
  • 邮箱:可选提交邮箱,该信息不会外泄,或将上线管理员回复邮件通知
  • 网址:可选提交网址,评论区该地址将以外链的形式展示在您的昵称上
  • 记忆:浏览器将记忆您已选择或填写过得信息,下次评论无需重复输入
  • 审核:提供一个和谐友善的评论环境,本站所有评论需要经过人工审核