一、前言¶
本文主要以下几方面介绍Go语言中接口:
- 初识接口
- 接口使用
- 空接口
- 类型断言
- 接口嵌套
二、初识接口¶
2.1 理解接口¶
对于Go语言中的接口,我们可以从现实生活出发,可以更好的理解Go语言中的接口。比如:
- USB 接口:想象你有各种不同的 USB 设备,如键盘、鼠标、打印机等。这些设备都可以通过 USB 接口连接到计算机,而计算机并不关心设备的具体类型。USB 接口定义了连接设备的标准方法,就像 Go 接口定义了方法的标准。计算机可以使用相同的 USB 接口与各种设备进行通信,而不需要知道设备的具体细节。
- 电源插座:在家里,我们可能有各种电器和设备,如电视、冰箱、洗衣机等,它们都需要插入电源插座来获取电力。电源插座就像一个接口,它规定了电器可以获取电力的方式,而不关心电器的种类。我们可以插入不同类型的电器,只要它们符合插座的标准。
- 汽车的启动按钮:现代汽车通常都有一个启动按钮,而不再使用传统的钥匙。这个按钮就像一个接口,它规定了启动汽车的标准操作方式。无论我们开的是丰田、本田还是其他品牌的汽车,只要它们都有相同的启动按钮接口,就可以使用相同的方式启动它们。
- 音响遥控器:音响遥控器上有各种按钮,如音量控制、播放/暂停、切换频道等。这些按钮就像接口中的方法,它们定义了可以对音响执行的操作。无论我们使用哪个品牌的音响,只要它们都支持相同的遥控器按钮接口,就可以使用相同的遥控器来控制它们。
总而言之,我们可以理解接口是一种约定,规定了可以执行的操作,而不关心操作的具体实现方法。
2.2 基本含义¶
Go语言的接口(interface)是一种类型,定义了一组方法的集合,但是接口又需要去实现它们,这些方法可以被不同的类型实现,进而就是这个类型实现了这个接口。
在Go语言中,接口是一个重要的概念,接口被广泛应用于许多标准库和框架中。通过接口,可以使不同的类型之间实现相同的行为,从而达到代码复用和扩展的目的,并且可以实现不同类型之间的无缝切换。
2.3 基本语法¶
type 接口名称 interface {
// 定义接口的方法以及这个方法的返回值
方法名称(传参) 返回值
方法名称(传参) 返回值
方法名称(传参) 返回值
方法名称(传参) 返回值
}
其中接口名称命名规则如下:
- 一个方法:方法名+er
- 两个方法:方法名1方法名2(驼峰命名)+er
- 三个方法及以上:命名见名知义即可
三、接口使用¶
3.1 不使用接口问题¶
下面模拟演示将Mysql数据库替换成PostgreSQL,将下面代码粘贴到00-interface-00.go文件中并保存该文件
package main
import "fmt"
// 1. 创建一个数据库的结构体,用来存放数据库的连接信息
type DBConfig struct {
User string
Password string
Host string
Port int
Database string
}
func main() {
// 2.声明一个数据库实例
db := DBConfig{"root", "password", "127.0.0.1", 3306, "interface_test"}
fmt.Println("数据库的配置:", db)
// 3.数据库操作,CRUD
fmt.Println("在Mysql中插入一条数据:db.row('insert xxx to user').Rows()")
// 4.将mysql替换成PG
// 4.1 生成一个PG数据库连接实例
dbPG := DBConfig{"root", "password", "127.0.0.1", 3306, "interface_test"}
fmt.Println("数据库的配置:", dbPG)
// 4.2 在PG中插入一条数据
fmt.Println("在PG中插入一条数据:db.QueryRow('insert xxx to user')")
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 导入包
import "fmt"
这部分导入了 Go 语言标准库中的 fmt 包,用于格式化输出。
部分 2 - 创建数据库连接信息结构体
// 1. 创建一个数据库的结构体,用来存放数据库的连接信息
type DBConfig struct {
User string
Password string
Host string
Port int
Database string
}
在这个部分,创建了一个名为 DBConfig 的结构体,用于存储数据库的连接信息,包括用户名、密码、主机、端口和数据库名。
部分 3 - 创建数据库实例和输出配置信息
// 2.声明一个数据库实例
db := DBConfig{"root", "password", "127.0.0.1", 3306, "interface_test"}
fmt.Println("数据库的配置:", db)
这一部分声明了一个名为 db 的数据库实例,使用了之前定义的 DBConfig 结构体来存储连接信息。然后,通过 fmt.Println 打印了数据库的配置信息。
部分 4 - 模拟数据库操作
// 3.数据库操作,CRUD
fmt.Println("在Mysql中插入一条数据:db.row('insert xxx to user').Rows()")
这部分代码注释了数据库操作的模拟,但没有实际执行任何操作。它仅是一个描述性的输出,表明了想要在 MySQL 数据库中插入一条数据。
部分 5 - 替换 MySQL 为 PostgreSQL
// 4.将mysql替换成PG
// 4.1 生成一个PG数据库连接实例
dbPG := DBConfig{"root", "password", "127.0.0.1", 3306, "interface_test"}
fmt.Println("数据库的配置:", dbPG)
// 4.2 在PG中插入一条数据
fmt.Println("在PG中插入一条数据:db.QueryRow('insert xxx to user')")
这部分代码描述了将 MySQL 替换为 PostgreSQL 的步骤。首先,创建了一个名为 dbPG 的 PostgreSQL 数据库连接实例,然后打印了 PostgreSQL 数据库的配置信息。最后,描述了在 PostgreSQL 中插入一条数据的操作,但同样,这也仅是描述性的输出,没有实际执行任何数据库操作。
假设要将上面的Mysql替换成PG,如果不使用接口,过程会变得很繁琐。主要体现以下几个方面:
- 代码重复性高:如果不使用接口,我们需要为每个不同的数据库引擎编写不同的数据库操作代码,这会导致代码重复性高,维护困难。每次切换数据库引擎时,都需要修改大量的代码。
- 耦合度高:没有接口,代码通常会紧密耦合到特定的数据库引擎上。这意味着如果我们想在应用程序中切换到不同的数据库引擎,就需要修改大量的代码,而不仅仅是数据库连接配置。
- 难以测试:没有接口,编写单元测试或模拟数据库操作变得更加困难,因为我们无法轻松地替换数据库操作的实际实现。
总而言之,使用接口可以降低代码的耦合度,提高可维护性,减少代码的重复性,使代码更具扩展性。这使得应用程序更容易适应变化,例如切换到不同的数据库引擎,而不需要大规模的代码修改。
3.2 接口基本使用¶
下面模拟演示使用接口来封装具体的数据库操作,并在需要时轻松切换到不同的数据库引擎,而无需修改使用接口的代码。
将下面代码粘贴到01-interface-00.go文件中并保存该文件
package main
import "fmt"
type DBCommon interface {
Insert(string) error
Update(string) error
Delete(string) error
}
// 1. 创建一个数据库的结构体,用来存放数据库的连接信息
type DBConfig struct {
User string
Password string
Host string
Port int
Database string
}
// 定义一个类型实现这个接口
type Mysql struct {
config DBConfig
charSet string
}
func (m Mysql) Insert(data string) error {
fmt.Println("插入数据到Mysql:", data)
return nil
}
func (m Mysql) Update(data string) error {
fmt.Println("更新数据到Mysql:", data)
return nil
}
func (m Mysql) Delete(data string) error {
fmt.Println("删除数据到Mysql:", data)
return nil
}
func main() {
// 生成数据库实例
db := DBConfig{"root", "password", "127.0.0.1", 3306, "interface_test"}
// 1.声明一个接口的变量
var dbCommonInterface DBCommon
var m Mysql
m.config = db
m.charSet = "utf-8"
dbCommonInterface = m
dbCommonInterface.Insert("insert")
dbCommonInterface.Update("update")
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 导入包
import (
"fmt"
)
这种格式可以帮助你按照不同的代码部分进行理解和分析。
部分 2 - 定义数据库操作接口
// 2. 定义数据库操作接口
type DBCommon interface {
Insert(string) error
Update(string) error
Delete(string) error
}
在这个部分,定义了一个名为 DBCommon 的接口,其中包含了三个数据库操作方法:Insert、Update 和 Delete。这个接口将用于定义数据库操作的通用契约。
部分 3 - 创建数据库连接信息结构体
// 3. 创建数据库连接信息结构体
type DBConfig struct {
User string
Password string
Host string
Port int
Database string
}
这一部分创建了一个名为 DBConfig 的结构体,用于存储数据库连接信息,包括用户名、密码、主机、端口和数据库名。
部分 4 - 定义实现接口的类型
// 4. 定义实现接口的类型
type Mysql struct {
config DBConfig
charSet string
}
func (m Mysql) Insert(data string) error {
fmt.Println("插入数据到Mysql:", data)
return nil
}
func (m Mysql) Update(data string) error {
fmt.Println("更新数据到Mysql:", data)
return nil
}
func (m Mysql) Delete(data string) error {
fmt.Println("删除数据到Mysql:", data)
return nil
}
在这个部分,定义了一个名为 Mysql 的类型,它包含了一个 DBConfig 实例和一个字符集字段。然后,为 Mysql 类型实现了 DBCommon 接口中定义的三个方法:Insert、Update 和 Delete。每个方法都打印了相应的操作,并返回了一个 error 类型的值,尽管在这个示例中始终返回了 nil。
部分 5 - 在 main 函数中使用接口
func main() {
// 5. 在 main 函数中使用接口
// 生成数据库实例
db := DBConfig{"root", "password", "127.0.0.1", 3306, "interface_test"}
// 1.声明一个接口的变量
var dbCommonInterface DBCommon
var m Mysql
m.config = db
m.charSet = "utf-8"
dbCommonInterface = m
dbCommonInterface.Insert("insert")
dbCommonInterface.Update("update")
}
这部分代码在 main 函数中创建了一个 DBConfig 实例,然后创建了一个 Mysql 类型的变量,并将数据库连接信息和字符集赋给它。接下来,你声明了一个 DBCommon 接口类型的变量 dbCommonInterface 并将 Mysql 类型的实例赋给它。最后,调用了接口的方法 Insert 和 Update,这些方法将调用 Mysql 类型中相应的方法来模拟数据库操作。
运行上面代码
$ go run .\01-interface-00.go
插入数据到Mysql: insert
更新数据到Mysql: update
假设要将上面的Mysql替换成PG,此时我们只需要新定义一下PG实现接口的类型和新声明一个接口的变量即可,其他地方不同进行修改。
将下面代码粘贴到01-interface-01.go文件中并保存该文件
package main
import "fmt"
type DBCommon interface {
Insert(string) error
Update(string) error
Delete(string) error
}
// 1. 创建一个数据库的结构体,用来存放数据库的连接信息
type DBConfig struct {
User string
Password string
Host string
Port int
Database string
}
// 定义一个类型实现这个接口
type Mysql struct {
config DBConfig
charSet string
}
func (m Mysql) Insert(data string) error {
fmt.Println("插入数据到Mysql:", data)
return nil
}
func (m Mysql) Update(data string) error {
fmt.Println("更新数据到Mysql:", data)
return nil
}
func (m Mysql) Delete(data string) error {
fmt.Println("删除数据到Mysql:", data)
return nil
}
// 定义一个类型实现这个接口
type PostgreSQL struct {
config DBConfig
charSet string
}
func (p PostgreSQL) Insert(data string) error {
fmt.Println("插入数据到PostgreSQL:", data)
return nil
}
func (p PostgreSQL) Update(data string) error {
fmt.Println("更新数据到PostgreSQL:", data)
return nil
}
func (p PostgreSQL) Delete(data string) error {
fmt.Println("删除数据到PostgreSQL:", data)
return nil
}
func main() {
// 生成数据库实例
db := DBConfig{"root", "password", "127.0.0.1", 3306, "interface_test"}
// 1.声明一个接口的变量
var dbCommonInterface DBCommon
var m PostgreSQL
// var m Mysql
m.config = db
m.charSet = "utf-8"
dbCommonInterface = m
dbCommonInterface.Insert("insert")
dbCommonInterface.Update("update")
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 导入包
import (
"fmt"
)
这种格式可以帮助你按照不同的代码部分进行理解和分析。
部分 2 - 定义数据库操作接口
// 2. 定义数据库操作接口
type DBCommon interface {
Insert(string) error
Update(string) error
Delete(string) error
}
在这个部分,定义了一个名为 DBCommon 的接口,其中包含了三个数据库操作方法:Insert、Update 和 Delete。这个接口将用于定义数据库操作的通用契约。
部分 3 - 创建数据库连接信息结构体
// 3. 创建数据库连接信息结构体
type DBConfig struct {
User string
Password string
Host string
Port int
Database string
}
这一部分创建了一个名为 DBConfig 的结构体,用于存储数据库连接信息,包括用户名、密码、主机、端口和数据库名。
部分 4 - 定义实现接口的类型 (MySQL 类型)
// 4.1 定义一个类型实现这个接口,MySQL 类型
type Mysql struct {
config DBConfig
charSet string
}
func (m Mysql) Insert(data string) error {
fmt.Println("插入数据到 MySQL:", data)
return nil
}
func (m Mysql) Update(data string) error {
fmt.Println("更新数据到 MySQL:", data)
return nil
}
func (m Mysql) Delete(data string) error {
fmt.Println("删除数据到 MySQL:", data)
return nil
}
在这个部分,定义了一个名为 Mysql 的类型,它包含了一个 DBConfig 实例和一个字符集字段。然后,你为 Mysql 类型实现了 DBCommon 接口中定义的三个方法:Insert、Update 和 Delete。每个方法都打印了相应的操作,并返回了一个 error 类型的值,尽管在这个示例中始终返回了 nil。
部分 5 - 定义实现接口的类型 (PostgreSQL 类型)
// 4.2 定义另一个类型实现这个接口,PostgreSQL 类型
type PostgreSQL struct {
config DBConfig
charSet string
}
func (p PostgreSQL) Insert(data string) error {
fmt.Println("插入数据到 PostgreSQL:", data)
return nil
}
func (p PostgreSQL) Update(data string) error {
fmt.Println("更新数据到 PostgreSQL:", data)
return nil
}
func (p PostgreSQL) Delete(data string) error {
fmt.Println("删除数据到 PostgreSQL:", data)
return nil
}
在这个部分,定义了另一个名为 PostgreSQL 的类型,它也包含了一个 DBConfig 实例和一个字符集字段。然后,为 PostgreSQL 类型实现了 DBCommon 接口中定义的三个方法:Insert、Update 和 Delete。每个方法都打印了相应的操作,并返回了一个 error 类型的值,同样在这个示例中始终返回了 nil。
部分 6 - 在 main 函数中使用接口 (PostgreSQL 类型)
func main() {
// 生成数据库实例
db := DBConfig{"root", "password", "127.0.0.1", 3306, "interface_test"}
// 1.声明一个接口的变量
var dbCommonInterface DBCommon
var m PostgreSQL
// var m Mysql
m.config = db
m.charSet = "utf-8"
dbCommonInterface = m
dbCommonInterface.Insert("insert")
dbCommonInterface.Update("update")
}
在这个部分代码中,创建了一个名为 db 的 DBConfig 实例,然后创建了一个 PostgreSQL 类型的变量 m,并将数据库连接信息和字符集赋给它。接着,声明了一个 DBCommon 接口类型的变量 dbCommonInterface,并将 PostgreSQL 类型的实例赋给它。最后,调用了接口的方法 Insert 和 Update,这些方法将调用 PostgreSQL 类型中相应的方法来模拟数据库操作。
再次运行修改后的代码,观察到通过接口,我们可以轻松切换到不同的数据库引擎(MySQL 或 PostgreSQL),而无需修改使用接口的代码。
$ go run .\01-interface-01.go
插入数据到PostgreSQL: insert
更新数据到PostgreSQL: update
3.3 使用接口解决底层不兼容问题¶
假设程序既要兼容mysql、pg又要兼容sqlserver。此时我们也可以使用接口实现:
将下面代码粘贴到02-interface-00.go文件中并保存该文件
package main
import "fmt"
type DBCommon interface {
Insert(string) error
Update(string) error
Delete(string) error
}
// 1. 创建一个数据库的结构体,用来存放数据库的连接信息
type DBConfig struct {
User string
Password string
Host string
Port int
Database string
}
// 定义一个类型实现这个接口
type Mysql struct {
config DBConfig
charSet string
}
func (m Mysql) Insert(data string) error {
fmt.Println("插入数据到Mysql:", data)
return nil
}
func (m Mysql) Update(data string) error {
fmt.Println("更新数据到Mysql:", data)
return nil
}
func (m Mysql) Delete(data string) error {
fmt.Println("删除数据到Mysql:", data)
return nil
}
// 定义一个类型实现这个接口
type PostgreSQL struct {
config DBConfig
charSet string
}
func (p PostgreSQL) Insert(data string) error {
fmt.Println("插入数据到PostgreSQL:", data)
return nil
}
func (p PostgreSQL) Update(data string) error {
fmt.Println("更新数据到PostgreSQL:", data)
return nil
}
func (p PostgreSQL) Delete(data string) error {
fmt.Println("删除数据到PostgreSQL:", data)
return nil
}
// 定义一个类型实现这个接口
type SQLServer struct {
config DBConfig
charSet string
}
func (s SQLServer) Insert(data string) error {
fmt.Println("插入数据到SQLServer:", data)
return nil
}
func (s SQLServer) Update(data string) error {
fmt.Println("更新数据到SQLServer:", data)
return nil
}
func (s SQLServer) Delete(data string) error {
fmt.Println("删除数据到SQLServer:", data)
return nil
}
func main() {
dbType := "mysql"
// 生成数据库实例
// 1.声明一个接口的变量
var dbCommonInterface DBCommon
switch dbType {
case "mysql":
var m Mysql
dbCommonInterface = m
case "pg":
var pg PostgreSQL
dbCommonInterface = pg
case "sqlserver":
var sqls SQLServer
dbCommonInterface = sqls
}
dbCommonInterface.Insert("insert")
dbCommonInterface.Update("update")
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 导入包
import "fmt"
这部分代码导入了 Go 语言标准库中的 "fmt" 包,该包提供了格式化输入和输出的功能,用于在程序中进行文本输出。
部分 2 - 定义数据库操作接口
type DBCommon interface {
Insert(string) error
Update(string) error
Delete(string) error
}
这一部分定义了一个接口 DBCommon,该接口包含了三个方法:Insert、Update 和 Delete,用于数据库操作。这个接口将作为不同数据库引擎的通用契约。
部分 3 - 创建数据库连接信息结构体
type DBConfig struct {
User string
Password string
Host string
Port int
Database string
}
这部分代码创建了一个结构体 DBConfig,用于存储数据库连接信息,包括用户名、密码、主机、端口和数据库名称。这个结构体将用于配置不同数据库引擎的连接信息。
部分 4 - 定义实现接口的类型 (Mysql 类型)
type Mysql struct {
config DBConfig
charSet string
}
func (m Mysql) Insert(data string) error {
fmt.Println("插入数据到 Mysql:", data)
return nil
}
func (m Mysql) Update(data string) error {
fmt.Println("更新数据到 Mysql:", data)
return nil
}
func (m Mysql) Delete(data string) error {
fmt.Println("删除数据到 Mysql:", data)
return nil
}
这一部分代码定义了一个名为 Mysql 的类型,它包含了一个 DBConfig 实例和一个字符集字段。然后,为 Mysql 类型实现了 DBCommon 接口中定义的三个方法:Insert、Update 和 Delete。每个方法都打印了相应的操作,并返回了一个 error 类型的值,尽管在这个示例中始终返回了 nil。
部分 5 - 定义实现接口的类型 (PostgreSQL 类型)
type PostgreSQL struct {
config DBConfig
charSet string
}
func (p PostgreSQL) Insert(data string) error {
fmt.Println("插入数据到 PostgreSQL:", data)
return nil
}
func (p PostgreSQL) Update(data string) error {
fmt.Println("更新数据到 PostgreSQL:", data)
return nil
}
func (p PostgreSQL) Delete(data string) error {
fmt.Println("删除数据到 PostgreSQL:", data)
return nil
}
这一部分代码与第三部分类似,定义了一个名为 PostgreSQL 的类型,它也包含了一个 DBConfig 实例和一个字符集字段。然后,为 PostgreSQL 类型实现了 DBCommon 接口中定义的三个方法:Insert、Update 和 Delete。每个方法都打印了相应的操作,并返回了一个 error 类型的值,同样在这个示例中始终返回了 nil。
部分 6 - 定义实现接口的类型 (SQLServer 类型)
type SQLServer struct {
config DBConfig
charSet string
}
func (s SQLServer) Insert(data string) error {
fmt.Println("插入数据到 SQLServer:", data)
return nil
}
func (s SQLServer) Update(data string) error {
fmt.Println("更新数据到 SQLServer:", data)
return nil
}
func (s SQLServer) Delete(data string) error {
fmt.Println("删除数据到 SQLServer:", data)
return nil
}
这一部分代码与前两部分类似,定义了一个名为 SQLServer 的类型,它也包含了一个 DBConfig 实例和一个字符集字段。然后,为 SQLServer 类型实现了 DBCommon 接口中定义的三个方法:Insert、Update 和 Delete。每个方法都打印了相应的操作,并返回了一个 error 类型的值,同样在这个示例中始终返回了 nil。
部分 7 - 在 main 函数中使用接口
func main() {
dbType := "mysql"
// 生成数据库实例
// 1.声明一个接口的变量
var dbCommonInterface DBCommon
switch dbType {
case "mysql":
var m Mysql
dbCommonInterface = m
case "pg":
var pg PostgreSQL
dbCommonInterface = pg
case "sqlserver":
var sqls SQLServer
dbCommonInterface = sqls
}
dbCommonInterface.Insert("insert")
dbCommonInterface.Update("update")
}
在 main 函数中,使用一个变量 dbType 来选择不同的数据库引擎,然后通过 switch 语句来创建相应的数据库实例,并将其赋值给接口变量 dbCommonInterface。接着,调用了接口的方法 Insert 和 Update,这些方法会根据所选的数据库引擎调用相应的方法来模拟数据库操作。
运行上面代码
$ go run .\02-interface-00.go
插入数据到Mysql: insert
更新数据到Mysql: update
将dbType的值替换成pg,如下
dbType := "pg"
再次运行上面代码
$ go run .\02-interface-00.go
插入数据到PostgreSQL: insert
更新数据到PostgreSQL: update
将dbType的值替换成sqlserver,如下
dbType := "sqlserver"
再次运行上面代码
$ go run .\02-interface-00.go
插入数据到SQLServer: insert
更新数据到SQLServer: update
四、空接口¶
4.1 基本含义¶
Go语言中的空接口(interface{})是一种特殊类型的接口,它不包含任何方法声明。因此,空接口可以表示任何类型的值。这使得空接口非常灵活,但也需要谨慎使用,因为它在编译时不提供类型检查,容易引发运行时错误。
4.2 基本语法¶
Go语言中空接口的基本语法非常简单,因为空接口不包含任何方法声明。定义空接口有两种方式,所以其基本语法也对应两种:
方式一:使用var
var variableName interface{}
其中:
var:用于声明变量的关键字。variableName:变量的名称,可以根据需要自行命名。interface{}:表示空接口类型。这里的大括号内为空,表示这是一个空接口,可以存储任何类型的值。
方式二:使用type
type MyEmptyInterface interface{}
在这里:
type:是Go语言的关键字,用于定义新的类型。MyEmptyInterface:是为空接口定义的自定义名称。可以根据需要选择一个有描述性的名称。
4.3 应用场景¶
空接口应用场景一般包含以下几种:
- 在处理不确定类型的数据时,例如解析JSON或XML数据时,可以使用空接口来存储未知类型的字段。
- 在通用函数中,可以接受不同类型的参数并执行通用操作。
- 在数据结构中,可以使用空接口来存储多种类型的元素,创建更灵活的数据结构。
4.4 示例说明¶
下面演示如何使用空接口来存储不同类型的值以及不同方式定义空接口。
将下面代码粘贴到03-interface-00.go文件中并保存该文件
package main
import "fmt"
type EmptyInterface interface{}
// 方式一:定义一个空接口
func main() {
//
fmt.Println("打印一条数据")
fmt.Println(666)
var ei EmptyInterface
s1 := "这是一个字符串"
ei = s1
fmt.Print("ei:", ei)
// 方式二:定义一个空接口
var x interface{}
x = 66
fmt.Println(x)
x = "Hello"
fmt.Println(x)
x = true
fmt.Println(x)
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 导入包
import "fmt"
这部分代码导入了 Go 语言标准库中的 "fmt" 包,该包提供了格式化输入和输出的功能,用于在程序中进行文本输出。
部分 2 - 自定义空接口类型
type EmptyInterface interface{}
在这里,定义了一个名为EmptyInterface的自定义空接口类型。这个空接口没有任何方法声明,因此可以用于存储任何类型的值。
部分 3 - 主函数
func main() {
// 代码内容...
}
这是程序的主函数入口,所有的代码都会在这里开始执行。
部分 4 - 打印一条数据
fmt.Println("打印一条数据")
这行代码用于打印文本字符串"打印一条数据"。
部分 5 - 打印整数、字符串和布尔值
fmt.Println(666)
var ei EmptyInterface
s1 := "这是一个字符串"
ei = s1
fmt.Print("ei:", ei)
var x interface{}
x = 66
fmt.Println(x)
x = "Hello"
fmt.Println(x)
x = true
fmt.Println(x)
这部分代码演示了如何使用空接口来存储不同类型的值。具体的步骤包括:
- 打印整数666。
- 声明一个
EmptyInterface类型的变量ei,将字符串"s1"赋值给它,然后打印ei的值。 - 声明一个内置的空接口类型
interface{}的变量x,依次将整数、字符串和布尔值赋值给它,然后打印x的值。
运行上面代码
$ go run .\03-interface-00.go
打印一条数据
666
ei:这是一个字符串66
Hello
true
五、类型断言¶
5.1 基本含义¶
在Go语言中,断言通常是指使用类型断言(Type Assertion)来将接口中的值转换为具体的类型,以便进一步操作或检查其类型。类型断言是一种在运行时检查接口的底层具体类型的方法。
5.2 基本语法¶
类型断言的基本语法如下:
value, ok := someInterface.(ConcreteType)
value:是一个变量,用于存储转换后的具体类型的值。ok:是一个布尔值,用于指示类型断言是否成功。someInterface:是一个接口变量,你想要从中获取具体类型的值。ConcreteType:是你期望的具体类型。
5.3 工作原理¶
结合5.2基本语法看,如果someInterface包含一个ConcreteType类型的值,那么value将存储该值,并且ok将为true;如果someInterface不包含ConcreteType类型的值,那么value将存储ConcreteType的零值(或者说默认值),并且ok将为false。
5.4 示例说明¶
下面演示如何使用类型断言和类型开关处理不同类型的空接口,以及如何根据接口中存储的具体类型执行不同的操作。
将下面代码粘贴到04-interface-00.go文件中并保存该文件
package main
import "fmt"
// 知道内容类型
func Data(data interface{}) {
t, ok := data.(string)
if ok {
fmt.Println("当前类型为string,变量值是:", t)
} else {
fmt.Println("data不是字符串")
fmt.Println("当前t的值:", t) // 返回空值
}
}
// 不知道内容类型
func getType(i interface{}) {
switch t := i.(type) {
case int:
fmt.Println("当前值为init类型:", t)
case string:
fmt.Println("当前值为string类型:", t)
case bool:
fmt.Println("当前值为bool类型:", t)
default:
fmt.Println("当前类型不在处理范围")
}
}
func main() {
s := "字符串"
Data(s)
i := 66
Data(i)
getType(i)
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 导入包
import "fmt"
这部分代码导入了 Go 语言标准库中的 "fmt" 包,该包提供了格式化输入和输出的功能,用于在程序中进行文本输出。
部分 2 - Data 函数
// 知道内容类型
func Data(data interface{}) {
t, ok := data.(string)
if ok {
fmt.Println("当前类型为string,变量值是:", t)
} else {
fmt.Println("data不是字符串")
fmt.Println("当前t的值:", t) // 返回空值
}
}
这是一个函数Data,它接受一个空接口作为参数data。函数首先尝试将data类型断言为字符串类型,如果成功,则打印出字符串类型的值和一个成功的消息,否则打印出一个失败的消息以及t的值(零值)。
部分 3 - getType 函数
// 不知道内容类型
func getType(i interface{}) {
switch t := i.(type) {
case int:
fmt.Println("当前值为int类型:", t)
case string:
fmt.Println("当前值为string类型:", t)
case bool:
fmt.Println("当前值为bool类型:", t)
default: // 正确写法是 default:
fmt.Println("当前类型不在处理范围")
}
}
这是一个函数getType,它也接受一个空接口作为参数i。函数使用类型开关(type switch)来检查i的具体类型,并根据类型执行不同的操作。如果类型匹配了int、string或bool,则打印相应类型的值,否则打印一个默认的消息。
部分 4 - 主函数 main
func main() {
s := "字符串"
Data(s) // 调用 Data 函数,输出 "当前类型为string,变量值是: 字符串"
i := 66
Data(i) // 调用 Data 函数,输出 "data不是字符串" 和 "当前t的值: "
getType(i) // 调用 getType 函数,输出 "当前值为int类型: 66"
}
这是程序的主函数。在主函数中,创建了一个字符串s和一个整数i,然后分别将它们传递给了Data和getType函数。根据参数的不同类型,函数的行为有所不同,输出也会相应地变化。
运行上面代码
$ go run .\04-interface-00.go
当前类型为string,变量值是: 字符串
data不是字符串
当前t的值:
当前值为init类型: 66
六、接口嵌套¶
6.1 基本含义¶
在Go语言中,可以嵌套接口,这允许你在一个接口内嵌套其他接口,以便构建更复杂的接口层次结构。接口嵌套通常用于将一组相关的方法组织成一个更大的接口,以便更好地管理和组织代码。
6.2 基本语法¶
type ParentInterface interface {
// 父接口的方法声明
}
type ChildInterface interface {
ParentInterface // 嵌套父接口
// 子接口的方法声明
}
这里有两个接口:ParentInterface 和 ChildInterface。
ParentInterface是父接口,它包含一些方法的声明。ChildInterface是子接口,通过ParentInterface进行嵌套,表示ChildInterface继承了ParentInterface中的方法。子接口可以包含自己的方法声明,也可以继承父接口中的方法声明。
6.3 接口嵌套作用¶
至于接口嵌套作用一般有以下几点:
- 减少代码重复:通过将一些通用的方法定义在父接口中,多个子接口可以继承这些方法,减少了代码的重复编写。这可以提高代码的可维护性,因为如果需要更改通用方法的实现,只需在一个地方进行修改。
- 实现多态:接口嵌套可以支持多态性,使不同类型的对象可以以相同的方式进行操作。这有助于简化代码,同时提高了代码的灵活性。
- 简化代码调用:通过将多个相关的方法集成到一个接口中,代码的使用者可以更轻松地调用这些方法,而不必考虑多个接口的细节。
- 模块化开发:接口嵌套可以支持模块化开发,不同团队或开发者可以独立地开发和测试各自的接口,然后将它们组合成更大的接口。
- 接口的适配器模式:通过接口嵌套,可以实现适配器模式,使不同的结构体可以适配同一个接口,从而实现了接口与实现的解耦。
6.4 示例说明¶
将下面代码粘贴到05-interface-00.go文件中并保存该文件
package main
import "fmt"
// 2. 定义一个 Animal 接口
type Animal interface {
Eat()
Sleep()
}
// 3. 定义一个 Dog 接口,嵌套 Animal 接口
type Dog interface {
Animal
}
// 4. 定义一个 Generic 接口
type Generic interface {
Login()
LogOut()
}
// 5. 定义一个 User 接口,嵌套 Generic 接口
type User interface {
Generic
}
// 6. 定义一个 VIPUser 接口,嵌套 Generic 接口,并添加 ConsulTation 方法
type VIPUser interface {
Generic
ConsulTation()
}
// 7. 定义一个 UserImpl 结构体
type UserImpl struct {
Name string
}
// 8. UserImpl 结构体实现 Login 方法
func (u UserImpl) Login() {
fmt.Println("用户已登录:", u.Name)
}
// 9. UserImpl 结构体实现 LogOut 方法
func (u UserImpl) LogOut() {
fmt.Println("用户已退出:", u.Name)
}
// 10. 定义一个 VIPUserImpl 结构体
type VIPUserImpl struct {
Name string
}
// 11. VIPUserImpl 结构体实现 Login 方法
func (u VIPUserImpl) Login() {
fmt.Println("VIP用户已登录:", u.Name)
}
// 12. VIPUserImpl 结构体实现 LogOut 方法
func (u VIPUserImpl) LogOut() {
fmt.Println("VIP用户已退出:", u.Name)
}
// 13. VIPUserImpl 结构体实现 ConsulTation 方法
func (u VIPUserImpl) ConsulTation() {
fmt.Println("VIP用户可以进行咨询:", u.Name)
}
// 14. 主函数
func main() {
// 15. 创建 User 接口的变量 userInterface,并分配 UserImpl 结构体实例给它
var userInterface User
var u UserImpl
u.Name = "zq"
userInterface = u
// 16. 调用 userInterface 的 Login 方法
userInterface.Login()
// 17. 创建 VIPUser 接口的变量 vipuserInterface,并分配 VIPUserImpl 结构体实例给它
var vipuserInterface VIPUser
var vipu VIPUserImpl
vipu.Name = "qz"
vipuserInterface = vipu
// 18. 调用 vipuserInterface 的 Login 方法
vipuserInterface.Login()
// 19. 调用 vipuserInterface 的 ConsulTation 方法
vipuserInterface.ConsulTation()
}
针对上面代码分为几个部分进行详细说明:
部分 1 - 导入包
import "fmt"
这部分代码导入了 Go 语言标准库中的 "fmt" 包,该包提供了格式化输入和输出的功能,用于在程序中进行文本输出。
部分 2 - 定义 Animal 接口和 Dog 接口
type Animal interface {
Eat()
Sleep()
}
type Dog interface {
Animal
}
这里定义了两个接口,Animal 和 Dog。Animal 接口包含两个方法,Eat() 和 Sleep(),而 Dog 接口嵌套了 Animal 接口,意味着 Dog 接口包含了 Animal 接口的方法。
部分 3 - 定义 Generic 接口
type Generic interface {
Login()
LogOut()
}
这里定义了一个名为 Generic 的接口,它包含了两个方法:Login() 和 LogOut()。这个接口用于表示通用的登录和登出功能。
部分 4 - 定义 User 接口
type User interface {
Generic
}
User 接口嵌套了 Generic 接口,这意味着 User 接口包含了 Generic 接口的方法。这种接口嵌套允许我们构建更高级别的接口,扩展现有接口的功能。
部分 5 - 定义 VIPUser 接口
type VIPUser interface {
Generic
ConsulTation()
}
与前面一样,VIPUser 接口也嵌套了 Generic 接口,并添加了一个额外的方法 ConsulTation(),用于表示VIP用户可以进行咨询的功能。
部分 6 - 定义 UserImpl 结构体
type UserImpl struct {
Name string
}
这里定义了一个名为 UserImpl 的结构体,表示用户的实现。它包含一个字段 Name,用于存储用户的名称。
部分 7 - 为 UserImpl 结构体实现 Login 方法
func (u UserImpl) Login() {
fmt.Println("用户已登录:", u.Name)
}
这里通过为 UserImpl 结构体定义一个方法 Login() 来实现 User 接口中的 Login() 方法。当调用 User 接口的 Login() 方法时,将执行此函数。
部分 8 - 为 UserImpl 结构体实现 LogOut 方法
func (u UserImpl) LogOut() {
fmt.Println("用户已退出:", u.Name)
}
同样,这里为 UserImpl 结构体定义了 LogOut() 方法,以实现 User 接口中的 LogOut() 方法。
部分 9 - 定义 VIPUserImpl 结构体
type VIPUserImpl struct {
Name string
}
与 UserImpl 结构体类似,这里定义了一个名为 VIPUserImpl 的结构体,表示VIP用户的实现。
部分 10 - 为 VIPUserImpl 结构体实现 Login 方法
func (u VIPUserImpl) Login() {
fmt.Println("VIP用户已登录:", u.Name)
}
通过为 VIPUserImpl 结构体定义 Login() 方法,实现了 VIPUser 接口中的 Login() 方法。
部分 11 - 为 VIPUserImpl 结构体实现 LogOut 方法
func (u VIPUserImpl) LogOut() {
fmt.Println("VIP用户已退出:", u.Name)
}
同样,这里为 VIPUserImpl 结构体定义了 LogOut() 方法,以实现 VIPUser 接口中的 LogOut() 方法。
部分 12 - 为 VIPUserImpl 结构体实现 ConsulTation 方法
// 部分 12 - 为 VIPUserImpl 结构体实现 ConsulTation 方法
func (u VIPUserImpl) ConsulTation() {
fmt.Println("VIP用户可以进行咨询:", u.Name)
}
最后,通过为 VIPUserImpl 结构体定义 ConsulTation() 方法,实现了 VIPUser 接口中的 ConsulTation() 方法。
部分 13 - 主函数
// 部分 13 - 主函数
func main() {
// 部分 14 - 创建 User 接口的变量 userInterface,并分配 UserImpl 结构体实例给它
var userInterface User
var u UserImpl
u.Name = "zq"
userInterface = u
// 部分 15 - 调用 userInterface 的 Login 方法
userInterface.Login()
// 部分 16 - 创建 VIPUser 接口的变量 vipuserInterface,并分配 VIPUserImpl 结构体实例给它
var vipuserInterface VIPUser
var vipu VIPUserImpl
vipu.Name = "qz"
vipuserInterface = vipu
// 部分 17 - 18 - 调用 vipuserInterface 的 Login 方法和 ConsulTation 方法
vipuserInterface.Login()
vipuserInterface.ConsulTation()
}
在主函数中,创建了 User 和 VIPUser 接口的实例,并将它们与相应的结构体实例关联。然后,调用接口的方法来执行相应的操作。
运行上面代码
$ go run .\05-interface-00.go
用户已登录: zq
VIP用户已登录: qz
VIP用户可以进行咨询: qz