Go 语言入门很简单 -- 13. Go 接口 #私藏项目实操分享#

简介: Go 语言入门很简单 -- 13. Go 接口 #私藏项目实操分享#

前面的文章中,了解到 Go 语言不是一种传统意义上的面向对象语言,因此 Go 没有类和继承的概念。

但是面向对象的功能很强大而且很实用, 前一篇文章中已经了解到可以通过嵌入类型来实现 Has-a 的关系。

这一篇文章将通过学习接口来看到 Go 通过结构体、方法和接口实现面向对象的功能。

在上一篇文章 《 12. Go 方法》 中,我们注意到命名了求矩形的面积的 area() 方法和圆面积的 area() 方法,在实际开发过程中,类似的关系还有很多。Go 语言通过 接口 类型将这些相似直接表示出来。

接口为 Go 语言实现了抽象的功能。接口的主体只能有方法声明和嵌入接口。接口中的方法没有函数体。只能在接口中定义方法签名,我们不能创建接口的对象,接口只为了实现,任何实现类型都需要提供方法的主体。

接口是定义一系列动作和行为的工具。它们帮助对象依赖抽象而不是其他对象的具体实现。我们可以通过对多个接口进行分组来组合不同的行为。

image.png

什么是接口

简单来说,接口是一组方法的组合,代表不同数据类型的常见行为。

通过接口,我们可以组织不同的方法组,适用于不同类型的对象。通过这样做,我们的程序可以依靠更高的抽象(接口),而不是具体的实现,允许其他方法与实现同一接口的各种不同对象一起工作。在面向对象的世界里,这个概念被称为依赖倒置原则(Dependency Inversion PrincipleSOLID)。

在 Go 中,构建小型接口然后将它们组合在一起以向对象添加更多功能被认为是最佳实践。通过这种方式,您可以保持代码整洁并提高可重用性。

在 Go 中,我们可以在一个结构体(对象)实现其所有方法时自动推断它实现了一个接口。如果某个对象实现了某个接口的所有方法,就表示它实现了该“接口”,无须显式地在该类型上添加接口说明。

定义一个接口

假设我们把圆和矩阵抽象出来 -- 都是形状,因此可以实现一个形状 Shape 的接口,代码如下:

type Shape interface {
    perimeter() float64
    area() float64
}

可以看到接口的定义也是 先使用 type 关键字,中间是接口名,后面是关键字 interface。但接口里面的主体不是定义字段,而是定义一些“方法集”。

方法集是一系列方法,为了去实现这个接口。即只定义原型而不用实现。

总结,定义接口的基本格式如下:

type interfaceName interface {
  methodName1(参数列表) (返回值)
  methodName2(参数列表) (返回值)
  ...
 }

Shape 是一个非常简单的接口,它定义了一个 area()  的方法。此方法表示其他对象可以实现的动作或行为。

在矩形和圆结构体中,都有返回面积( float )的方法,所以都可以实现 Shape

Note:

1. Go 语言中,接口命名时习惯性以“er”结尾,比如:Printer、Reader、Writer等,通常以动名词来命名。

2. Go 语言中,一个接口中包含的方法不应该过多,0~3 个即可。

例如:

package main
import "fmt"
// 定义结构体部分
type Person struct {
  Name string
}
type Student struct {
  Person
  School string
}
type Teacher struct {
  Person
  Department string
}
// 定义接口部分
type Speaker() interface {
  Talk()
}
type Learner interface {
  Talk()
  Study()
}
// 方法实现接口部分
func (p Person) Talk(){}
func (s Student) Talk(){}
func (t Student) Talk(){}
func (s Student) Study(){}

接口组合

在 Go 语言中,除了类型可以匿名组合,接口也可以组合在一起。将一个接口匿名嵌入到另一个接口中,就是接口组合(Interface-combination)

例如:

type SpeakLearner interface {
  Speaker
  Learner
}

在接口 SpeakLearner 中组合了 SpeakerLearner 两个接口,所以它既能做 Speaker 接口的所有事情,又能做 Learner 接口的所有事情。

接口的赋值

与其他面向对象编程语言不同,Go 语言的接口还可以赋值,如果定义了一个接口的变量,那么这个变量可以存储实现了这个接口的任意类型的对象。

例如上述的 Speaker 接口就可以存储对象 Person、Teacher 和 Student 的值。

package main
import "fmt"
// 定义结构体部分
type Person struct {
    Name string
}
type Student struct {
    Person
    School string
}
type Teacher struct {
    Person
    Department string
}
// 定义接口部分
type Speaker interface {
    Talk()
}
type Learner interface {
    Talk()
    Study()
}
// 对象方法实现
func (p Person) Talk() {
    fmt.Printf("Hi, 我是 %s. 你好~\n", p.Name)
}
func (s Student) Talk() {
    fmt.Printf("Hi, 我是 %s. 我是学生,正在 %s 学习.\n ", s.Name, s.School)
}
func (t Teacher) Talk() {
    fmt.Printf("Hi, 我是 %s. 我是老师,正在 %s 工作.\n", t.Name, t.Department)
}
func (s Student) Study() {
    fmt.Printf("我正在 %s 学习 Go 语言.\n", s.School)
}
func main() {
    p1 := Person{"张三"}
    t1 := Teacher{Person{"Lily"}, "计算机学院"}
    s1 := Student{Person{"Lee"}, "搬砖大学"}
    var s Speaker // 定义 Speaker 类型的变量 s
    s = p1        // s 能存储 People
    s.Talk()
    s = t1 // s 能存储 Teacher
    s.Talk()
    s = s1 // s 能存储 Student
    s.Talk()
    var l Learner
    l = s1 // l 能存储 Student
    l.Study()
}

运行结果为:

Hi, 我是 张三. 你好~
Hi, 我是 Lily. 我是老师,正在 计算机学院 工作.
Hi, 我是 Lee. 我是学生,正在 搬砖大学 学习.
我正在 搬砖大学 学习 Go 语言.

接口作为函数的参数

我们可以将接口类型作为函数的参数。

func totalArea(shapes ...Shape) float64 {
    var area float64
    for _, s := range shapes {
        area += s.area()
    }
    return area
}

可以这样调用函数:

fmt.Println(totalArea(&c, &r))

接口也可被用于字段:

type MultiShape struct {
    shapes []Shape
}

我们甚至可以把 MultiShape 本身变成一个 Shape,通过给它一个 它的面积方法。

func (m *MultiShape) area() float64 {
    var area float64
    for _, s := range m.shapes {
        area += s.area()
    }
    return area
}

现在, Multishape 可以包括 Circle s, Rectangle s 或者其他 MultiShape s.

空接口

在 Go 语言中,任何数据类型都默认实现了空接口,空接口可以这样定义:

type InterfaceName interface{
}

空接口也就是包含 0 个方法的接口,所以可以使用空接口定义任何类型的变参函数,如果一个函数返回空接口,就可以返回任意类型的值。

interface{}
// 函数 test1 返回 1 个interface{}
func test1(a interface{}) {}
// 函数 test2 可返回多个 interface{}
func test2(a ... interface{}) {}

空接口可以存储结构体、字符串、整数等任何类型。空接口增强了代码的扩展性与通用性。

Notes:

平时使用的输入输出函数 fmt.Println 的参数就是一个空接口,正因为如此,Println 函数可以打印多种类型

func Println(a ...interface{}) (n int, err error)

Go 语言中的空接口的作用类似于 C 语言中的 void * 、Java/C# 中的 System.Object

接口的比较

两个接口可以通过 == 或 != 进行比较,例如:

var a, b interface{}
fmt.Println(a == b )

接口的比较规则:

  • 动态值为 nil 的接口变量总是相等的
  • 如果只有 1 个接口为 nil,那么比较结果总是 false
  • 如果两个接口不为 nil 且接口变量具有相同的动态类型和动态类型值,那么两个接口是相同的
  • 如果接口存储的动态类型值是不可比较的,那么在运行时会报错

总结

  • 一个接口中的方法没有主体。一个接口的所有方法都是纯粹的抽象的。
  • 一个接口的方法可以有参数和返回类型。任何自定义类型都可以实现该接口。
  • 没有明确的语法来实现一个接口。当任何类型定义了接口的所有方法,那么该类型就隐含实现了该接口。
  • 一个实现了接口的类型也可以有其他方法
  • 一个类型可以实现一个以上的接口
  • 一个接口可以由许多类型实现
  • 当一个接口没有任何方法时,它被称为空接口。一个空接口在默认情况下是由所有类型实现
  • 一个接口可以作为结构的一个字段使用。使用时,我们需要在创建结构对象时传递接口的实际实现。创建结构体对象时传递接口的实际实现。
  • 在 Go 中,多态性可以用一个接口来实现。

相关文章
|
3月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
1月前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
Go 语言入门指南:切片
|
1月前
|
Go C语言
Go语言入门:分支结构
本文介绍了Go语言中的条件语句,包括`if...else`、`if...else if`和`switch`结构,并通过多个练习详细解释了它们的用法。`if...else`用于简单的条件判断;`if...else if`处理多条件分支;`switch`则适用于基于不同值的选择逻辑。特别地,文章还介绍了`fallthrough`关键字,用于优化重复代码。通过实例如判断年龄、奇偶数、公交乘车及成绩等级等,帮助读者更好地理解和应用这些结构。
40 15
|
4月前
|
存储 Rust Go
Go nil 空结构体 空接口有什么区别?
本文介绍了Go语言中的`nil`、空结构体和空接口的区别。`nil`是预定义的零值变量,适用于指针、管道等类型;空结构体大小为0,多个空结构体实例指向同一地址;空接口由`_type`和`data`字段组成,仅当两者均为`nil`时,空接口才为`nil`。
Go nil 空结构体 空接口有什么区别?
|
4月前
|
存储 设计模式 安全
Go语言中的并发编程:从入门到精通###
本文深入探讨了Go语言中并发编程的核心概念与实践技巧,旨在帮助读者从理论到实战全面掌握Go的并发机制。不同于传统的技术文章摘要,本部分将通过一系列生动的案例和代码示例,直观展示Go语言如何优雅地处理并发任务,提升程序性能与响应速度。无论你是Go语言初学者还是有一定经验的开发者,都能在本文中找到实用的知识与灵感。 ###
|
4月前
|
Serverless Go
Go语言中的并发编程:从入门到精通
本文将深入探讨Go语言中并发编程的核心概念和实践,包括goroutine、channel以及sync包等。通过实例演示如何利用这些工具实现高效的并发处理,同时避免常见的陷阱和错误。
|
3月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
|
4月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
62 3
|
4月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
54 3
|
7月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
294 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库