接口
接口介绍
语法
在Go中接口(interface)是一种类型,一种抽象的类型。接口(interface)是一组函数method的集合,Golang中的接口不能包含任何变量。
在Go中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序设计的多态和高内聚低耦合的思想。
Go中的接口也是一种数据类型,不需要显示实现。只需要一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。
Golang中每个接口由数个方法组成,接口的定义格式如下:
type 接口名 interface {
方法名1 (参数列表1) 返回值列表1
方法名2 (参数列表2) 返回值列表2
}
其中
- 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等,接口名最好突出该接口的类型含义。
- 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名是可以省略
案例
演示:定义一个Usber接口让Phone 和 Camera结构体实现这个接口
首先我们定义一个Usber接口,接口里面就定义了两个方法
// 定义一个Usber接口
type Usber interface {
start()
stop()
}
然后我们在创建一个手机结构体
// 如果接口里面有方法的话,必须要通过结构体或自定义类型实现这个接口
// 使用结构体来实现 接口
type Phone struct {
Name string
}
// 手机要实现Usber接口的话,必须实现usb接口的所有方法
func (p Phone) Start() {
fmt.Println(p.Name, "启动")
}
func (p Phone) Stop() {
fmt.Println(p.Name, "关闭")
}
然后我们在创建一个Phone的结构体,来实现这个接口
// 如果接口里面有方法的话,必须要通过结构体或自定义类型实现这个接口
// 使用结构体来实现 接口
type Phone struct {
Name string
}
// 手机要实现Usber接口的话,必须实现usb接口的所有方法
func (p Phone) start() {
fmt.Println(p.Name, "启动")
}
func (p Phone) stop() {
fmt.Println(p.Name, "关闭")
}
func main() {
var phone Usber = Phone{
"三星手机",
}
phone.start()
phone.stop()
}
我们在创建一个Camera结构体
// 使用相机结构体来实现 接口
type Camera struct {
Name string
}
// 相机要实现Usber接口的话,必须实现usb接口的所有方法
func (p Camera) start() {
fmt.Println(p.Name, "启动")
}
func (p Camera) stop() {
fmt.Println(p.Name, "关闭")
}
func main() {
var camera Usber = Camera{
"佳能",
}
camera.start()
camera.stop()
}
我们创建一个电脑的结构体,电脑的结构体就是用于接收两个实现了Usber的结构体,然后让其工作
// 电脑
type Computer struct {
}
// 接收一个实现了Usber接口的 结构体
func (computer Computer) Startup(usb Usber) {
usb.start()
}
// 关闭
func (computer Computer) Shutdown (usb Usber) {
usb.stop()
}
最后我们在main中调用方法
func main() {
var camera interfaceDemo.Camera = interfaceDemo.Camera{
"佳能",
}
var phone interfaceDemo.Phone = interfaceDemo.Phone{
"苹果",
}
var computer interfaceDemo.Computer = interfaceDemo.Computer{}
computer.Startup(camera)
computer.Startup(phone)
computer.Shutdown(camera)
computer.Shutdown(phone)
}
运行结果如下所示:
佳能 启动
苹果 启动
佳能 关闭
苹果 关闭
空接口
空接口(Empty Interface)是 Go 语言中的一个特殊类型,它不包含任何方法签名,因此可以表示任意类型的值。
// 空接口表示没有任何约束,任意的类型都可以实现空接口
type EmptyA interface {}
func main() {
var a EmptyA
var str = "你好golang"
// 让字符串实现A接口
a = str
fmt.Println(a)
}
同时golang中空接口也可以直接当做类型来使用,可以表示任意类型。
var a interface{}
a = 20
a = "hello"
a = true
空接口可以作为函数的参数,使用空接口可以接收任意类型的函数参数
// 空接口作为函数参数
func show(a interface{}) {
fmt.println(a)
}
map的值实现空接口
使用空接口实现可以保存任意值的字典
// 定义一个值为空接口类型
var studentInfo = make(map[string]interface{})
studentInfo["userName"] = "张三"
studentInfo["age"] = 15
studentInfo["isWork"] = true
slice切片实现空接口
// 定义一个空接口类型的切片
var slice = make([]interface{}, 4, 4)
slice[0] = "张三"
slice[1] = 1
slice[2] = true
类型断言
一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。
如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:
x.(T)
其中:
- x:表示类型为interface{}的变量
- T:表示断言x可能是的类型
该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败
package main
import (
"fmt"
)
func main() {
userInfo := make(map[string]interface{})
userInfo["userName"] = "zhangsan"
userInfo["age"] = 10
userInfo["hobby"] = []string{"吃饭", "睡觉"}
// 使用类型断言获取数组值
if hobby, ok := userInfo["hobby"].([]string); ok {
fmt.Println("Hobbies:", hobby) // 输出:Hobbies: [吃饭 睡觉]
} else {
fmt.Println("Hobby type assertion failed")
}
}
在 Go 中,一个空接口中存储了一个特定类型的数据,但在没有进行类型断言之前,Go 编译器只能将其视为一个空接口。
或者我们可以定义一个能传入任意类型的方法
package main
import (
"fmt"
)
func processInterfaceData(data interface{}) {
if intValue, ok := data.(int); ok {
fmt.Println("Received an integer:", intValue)
} else if stringValue, ok := data.(string); ok {
fmt.Println("Received a string:", stringValue)
} else if stringSliceValue, ok := data.([]string); ok {
fmt.Println("Received a slice of strings:", stringSliceValue)
} else {
fmt.Println("Received an unknown type")
}
}
func main() {
data1 := 42
data2 := "Hello, World!"
data3 := []string{"apple", "banana", "cherry"}
processInterfaceData(data1) // 输出:Received an integer: 42
processInterfaceData(data2) // 输出:Received a string: Hello, World!
processInterfaceData(data3) // 输出:Received a slice of strings: [apple banana cherry]
}
上面的示例代码中,如果要断言多次,那么就需要写很多if,这个时候我们可以使用switch语句来实现:
注意:
类型.(type)
只能结合switch语句使用
package main
import (
"fmt"
)
func processInterfaceData(data interface{}) {
switch value := data.(type) {
case int:
fmt.Println("Received an integer:", value)
case string:
fmt.Println("Received a string:", value)
case []string:
fmt.Println("Received a slice of strings:", value)
default:
fmt.Println("Received an unknown type")
}
}
func main() {
data1 := 42
data2 := "Hello, World!"
data3 := []string{"apple", "banana", "cherry"}
processInterfaceData(data1) // 输出:Received an integer: 42
processInterfaceData(data2) // 输出:Received a string: Hello, World!
processInterfaceData(data3) // 输出:Received a slice of strings: [apple banana cherry]
}
结构体接收者
值接收者
如果一个结构体(或类型)拥有使用值作为接收者的方法,那么无论你使用该结构体的值类型还是指针类型创建的实例,都可以将这些实例赋值给接口变量。
package main
import (
"fmt"
)
// 定义一个接口
type Shape interface {
Area() float64
}
// 定义一个矩形结构体
type Rectangle struct {
Width float64
Height float64
}
// 值接收者方法:计算矩形的面积
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func main() {
// 使用值类型实例化矩形结构体
rectValue := Rectangle{Width: 5, Height: 3}
// 使用指针类型实例化矩形结构体
rectPointer := &Rectangle{Width: 7, Height: 4}
// 将值类型实例赋值给接口变量
var shape1 Shape
shape1 = rectValue
fmt.Println("Area of rectValue:", shape1.Area()) // 输出:Area of rectValue: 15
// 将指针类型实例赋值给接口变量
var shape2 Shape
shape2 = rectPointer
fmt.Println("Area of rectPointer:", shape2.Area()) // 输出:Area of rectPointer: 28
}
结构体实现多个接口
一个结构体可以实现多个接口,这就意味着结构体需要提供满足每个接口所需的方法。
package main
import (
"fmt"
)
// 定义一个形状接口
type Shape interface {
Area() float64
}
// 定义一个可移动接口
type Movable interface {
Move(dx, dy float64)
}
// 定义一个结构体:矩形
type Rectangle struct {
Width float64
Height float64
}
// 值接收者方法:计算矩形的面积
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 指针接收者方法:移动矩形的位置
func (r *Rectangle) Move(dx, dy float64) {
r.Width += dx
r.Height += dy
}
func main() {
rect := &Rectangle{Width: 5, Height: 3}
// 将结构体实例赋值给接口变量
var shape Shape = rect
var movable Movable = rect
// 使用接口调用方法
fmt.Println("Area:", shape.Area()) // 输出:Area: 15
movable.Move(2, 3)
fmt.Printf("New Width: %.2f, New Height: %.2f\n", rect.Width, rect.Height)
// 输出:New Width: 7.00, New Height: 6.00
}
接口嵌套
接口嵌套是指在一个接口中嵌入(嵌套)其他接口,从而组合它们的方法集合,形成一个新的更大的接口。通过这种方式,新接口将继承嵌套的其他接口的方法,并且可以通过一个接口变量来调用这些方法,实现更多的功能组合。
package main
import "fmt"
// 定义一个通用的写日志接口
type Logger interface {
Log(message string)
}
// 定义一个数据库操作接口
type Database interface {
Insert(data interface{})
Query(query string) interface{}
}
// 定义一个高级接口,嵌套了 Logger 和 Database 接口
type AdvancedService interface {
Logger
Database
ProcessData(data interface{})
}
// 定义一个结构体实现 Logger 接口
type ConsoleLogger struct{}
func (cl ConsoleLogger) Log(message string) {
fmt.Println("Log:", message)
}
// 定义一个结构体实现 Database 接口
type InMemoryDatabase struct {
data map[string]interface{}
}
func (imd InMemoryDatabase) Insert(data interface{}) {
imd.data["key"] = data
}
func (imd InMemoryDatabase) Query(query string) interface{} {
return imd.data["key"]
}
// 实现 AdvancedService 接口
type MyAdvancedService struct {
Logger
Database
}
func (mas MyAdvancedService) ProcessData(data interface{}) {
mas.Log("Processing data...")
mas.Insert(data)
}
func main() {
logger := ConsoleLogger{}
db := InMemoryDatabase{data: make(map[string]interface{})}
// 使用 MyAdvancedService,结合了 Logger 和 Database 接口的功能
advService := MyAdvancedService{
Logger: logger,
Database: db,
}
advService.ProcessData("Hello, World!")
result := advService.Query("key")
fmt.Println("Query Result:", result)
}
// golang中空接口和类型断言
var userInfo = make(map[string]interface{})
userInfo["userName"] = "zhangsan"
userInfo["age"] = 10
userInfo["hobby"] = []string{"吃饭", "睡觉"}
fmt.Println(userInfo["userName"])
fmt.Println(userInfo["age"])
fmt.Println(userInfo["hobby"])
// 但是我们空接口如何获取数组中的值?发现 userInfo["hobby"][0] 这样做不行
// fmt.Println(userInfo["hobby"][0])
也就是我们的空接口,无法直接通过索引获取数组中的内容,因此这个时候就需要使用类型断言了
// 这个时候我们就可以使用类型断言了
hobbyValue,ok := userInfo["hobby"].([]string)
if ok {
fmt.Println(hobbyValue[0])
}
通过类型断言返回来的值,我们就能够直接通过角标获取了。