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 中,多态性可以用一个接口来实现。

目录
打赏
0
0
0
0
6
分享
相关文章
|
16天前
|
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
Go 语言入门指南:切片
|
1月前
|
Go语言入门:分支结构
本文介绍了Go语言中的条件语句,包括`if...else`、`if...else if`和`switch`结构,并通过多个练习详细解释了它们的用法。`if...else`用于简单的条件判断;`if...else if`处理多条件分支;`switch`则适用于基于不同值的选择逻辑。特别地,文章还介绍了`fallthrough`关键字,用于优化重复代码。通过实例如判断年龄、奇偶数、公交乘车及成绩等级等,帮助读者更好地理解和应用这些结构。
37 15
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
Go语言中的并发编程:从入门到精通###
本文深入探讨了Go语言中并发编程的核心概念与实践技巧,旨在帮助读者从理论到实战全面掌握Go的并发机制。不同于传统的技术文章摘要,本部分将通过一系列生动的案例和代码示例,直观展示Go语言如何优雅地处理并发任务,提升程序性能与响应速度。无论你是Go语言初学者还是有一定经验的开发者,都能在本文中找到实用的知识与灵感。 ###
Go nil 空结构体 空接口有什么区别?
本文介绍了Go语言中的`nil`、空结构体和空接口的区别。`nil`是预定义的零值变量,适用于指针、管道等类型;空结构体大小为0,多个空结构体实例指向同一地址;空接口由`_type`和`data`字段组成,仅当两者均为`nil`时,空接口才为`nil`。
Go nil 空结构体 空接口有什么区别?
Go语言中的并发编程:从入门到精通
本文将深入探讨Go语言中并发编程的核心概念和实践,包括goroutine、channel以及sync包等。通过实例演示如何利用这些工具实现高效的并发处理,同时避免常见的陷阱和错误。
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
在数字化时代,构建高可靠性服务架构至关重要。本文探讨了如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
95 1
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
Go语言的并发特性
【10月更文挑战第26天】Go语言的并发特性
37 1

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等