Go 入门很简单:Go 接口

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

引言

前面的文章中,了解到 Go 语言不是一种传统意义上的面向对象语言,因此 Go 没有类和继承的概念。但是面向对象的功能很强大而且很实用,Go 方法 的文章中已经了解到可以通过嵌入类型来实现 Has-a 的关系。

c9c84d874b816923d753881d1c93d983577c28.png

接口介绍

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

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


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

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


什么是接口

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

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


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


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


定义一个接口

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

typeShapeinterface {
perimeter() float64area() float64}

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

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


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

typeinterfaceNameinterface {
methodName1(参数列表) (返回值)methodName2(参数列表) (返回值)... }


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


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

Note:

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

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


例如:

packagemainimport"fmt"// 定义结构体部分typePersonstruct {
Namestring}
typeStudentstruct {
PersonSchoolstring}
typeTeacherstruct {
PersonDepartmentstring}
// 定义接口部分typeSpeaker() interface {
Talk()
}
typeLearnerinterface {
Talk()
Study()
}
// 方法实现接口部分func (pPerson) Talk(){}
func (sStudent) Talk(){}
func (tStudent) Talk(){}
func (sStudent) Study(){}

接口组合

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

例如:

typeSpeakLearnerinterface {
SpeakerLearner}

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

接口的赋值

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

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

packagemainimport"fmt"// 定义结构体部分typePersonstruct {
Namestring}
typeStudentstruct {
PersonSchoolstring}
typeTeacherstruct {
PersonDepartmentstring}
// 定义接口部分typeSpeakerinterface {
Talk()
}
typeLearnerinterface {
Talk()
Study()
}
// 对象方法实现func (pPerson) Talk() {
fmt.Printf("Hi, 我是 %s. 你好~\n", p.Name)
}
func (sStudent) Talk() {
fmt.Printf("Hi, 我是 %s. 我是学生,正在 %s 学习.\n ", s.Name, s.School)
}
func (tTeacher) Talk() {
fmt.Printf("Hi, 我是 %s. 我是老师,正在 %s 工作.\n", t.Name, t.Department)
}
func (sStudent) Study() {
fmt.Printf("我正在 %s 学习 Go 语言.\n", s.School)
}
funcmain() {
p1 :=Person{"张三"}
t1 :=Teacher{Person{"Lily"}, "计算机学院"}
s1 :=Student{Person{"Lee"}, "搬砖大学"}
varsSpeaker// 定义 Speaker 类型的变量 ss=p1// s 能存储 Peoples.Talk()
s=t1// s 能存储 Teachers.Talk()
s=s1// s 能存储 Students.Talk()
varlLearnerl=s1// l 能存储 Studentl.Study()
}


运行结果为:

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

接口作为函数的参数

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

functotalArea(shapes...Shape) float64 {
varareafloat64for_, s :=rangeshapes {
area+=s.area()
    }
returnarea}


可以这样调用函数:

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

接口也可被用于字段:

typeMultiShapestruct {
shapes []Shape}


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

func (m*MultiShape) area() float64 {
varareafloat64for_, s :=rangem.shapes {
area+=s.area()
    }
returnarea}

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

空接口

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

typeInterfaceNameinterface{
}

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

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

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

Notes:

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

funcPrintln(a...interface{}) (nint, errerror)


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

接口的比较

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

vara, binterface{}
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包等。通过实例演示如何利用这些工具实现高效的并发处理,同时避免常见的陷阱和错误。
|
5月前
|
安全 Go 开发者
破译Go语言中的并发模式:从入门到精通
在这篇技术性文章中,我们将跳过常规的摘要模式,直接带你进入Go语言的并发世界。你将不会看到枯燥的介绍,而是一段代码的旅程,从Go的并发基础构建块(goroutine和channel)开始,到高级模式的实践应用,我们共同探索如何高效地使用Go来处理并发任务。准备好,让Go带你飞。
|
6月前
|
存储 Go
Go to Learn Go之接口
Go to Learn Go之接口
46 7
|
5月前
|
存储 安全 Go
Go语言切片:从入门到精通的深度探索###
本文深入浅出地剖析了Go语言中切片(Slice)这一核心概念,从其定义、内部结构、基本操作到高级特性与最佳实践,为读者提供了一个全面而深入的理解。通过对比数组,揭示切片的灵活性与高效性,并探讨其在并发编程中的应用优势。本文旨在帮助开发者更好地掌握切片,提升Go语言编程技能。 ###
|
7月前
|
存储 安全 程序员