当前位置: 首页 > article >正文

12 go语言(golang) - 数据类型:接口

接口

在 Go 语言中,接口(interface)是一种抽象类型,它定义了一组方法的集合,但不包含这些方法的实现。是 Go 语言实现多态和面向对象编程的重要机制之一。

1、接口定义

在 Go 中,接口通过 type 关键字进行定义,并且包含一组方法签名。

type JDBCUtil interface {
	GetConnection() string
	CloseConnection(connection string) string
}

在上面的例子中,JDBCUtil 是一个接口,它要求任何实现该接口的类型都必须有 GetConnection()CloseConnection(connection string) 方法。

2、实现接口

Go 中不需要显式地声明某个类型实现了某个接口,只要该类型提供了与接口所需的方法相同的方法集即可。这被称为隐式实现。

type MysqlUtil struct {
	url string
}

func (m MysqlUtil) GetConnection() string {
	return "模拟获取连接:" + m.url
}

func (_ MysqlUtil) CloseConnection(connection string) string {
	return fmt.Sprintf("成功关闭连接:【%s】", connection)
}

在这个例子中,MysqlUtil 类型隐式地实现了 JDBCUtil 接口,因为它具有与 JDBCUtil 接口相匹配的方法集。

3、使用接口

可以将具体类型赋值给一个接口变量,从而通过该变量调用其方法。

func Test1(t *testing.T) {
	util := MysqlUtil{"jdbc://xxx..."}
	connection := util.GetConnection()
	fmt.Println(connection)
	status := util.CloseConnection(connection)
	fmt.Println(status)
}

输出:

模拟获取连接:jdbc://xxx...
成功关闭连接:【模拟获取连接:jdbc://xxx...】

4、空接口

空接口(interface{})是特殊的,它没有任何方法,因此所有类型都默认实现空接口。这使得空接口非常适合用于存储任意数据。

func Test2(t *testing.T) {
	var i interface{}
	i = "abc"
	fmt.Printf("值:%v,类型:%T \n", i, i)
	i = 123
	fmt.Printf("值:%v,类型:%T \n", i, i)
}

type AnyType interface {
}

func Test3(t *testing.T) {
	// 推荐简写为:list := []interface{}{1, "a", 3.14, true} ,这样就不用定义AnyType了
	var list = []AnyType{1, "a", 3.14, true} 
	
	for _, value := range list {
		fmt.Printf("值:%v,类型:%T \n", value, value)
	}
}

输出:

=== RUN   Test2
值:abc,类型:string 
值:123,类型:int 

=== RUN   Test3
值:1,类型:int 
值:a,类型:string 
值:3.14,类型:float64 
值:true,类型:bool 

5、类型断言

当使用空或通用性较强的接口时,你可能需要知道具体存储的数据是什么,这时可以使用类型断言来处理。

func Test4(t *testing.T) {
	var util = MysqlUtil{"jdbc://xxx..."}

	var i interface{} = util

	if _, ok := i.(JDBCUtil); ok {
		fmt.Println("JDBCUtil 实现了接口:MysqlUtil")
	} else {
		fmt.Println("JDBCUtil 没有实现接口:MysqlUtil")
	}
}

func Test5(t *testing.T) {
	var i interface{} = MysqlUtil{}
	switch i.(type) {
	case string:
		println("类型是string")
	case int:
		println("类型是int")
	default:
		println("未知类型")
	}
}

输出:

=== RUN   Test4
JDBCUtil 实现了接口:MysqlUtil

=== RUN   Test5
未知类型

6、多态

多态(Polymorphism)在计算机科学中指的是一种能力,即相同的操作可以作用于不同的数据类型上。在面向对象编程中,多态性通常意味着一个接口可以被多个类型实现,而这些类型可以在不修改调用代码的情况下被使用。

在 Go 语言中,多态性主要通过接口来实现。

多态性的优点

  • 灵活性和可扩展性:你只需确保新添加的类型符合某个既定行为(即,实现了特定的方法集),就能将其无缝地融入现有系统。

  • 代码重用和简化:通过抽象出公共行为,可以避免重复代码,并使得代码更易于维护和理解。

  • 解耦合设计:调用者无需知道具体对象是什么,只需关心它们是否符合所需行为。这种解耦合设计使得系统模块之间更加独立。

练习

在项目中实现注册成功之后向用户发送:邮件、手机的消息提醒。

// 首先,定义一个接口,它包含需要实现的方法。这些方法代表了某种行为或功能。
type Message interface {
	SendMsg() string
}

// 不同的具体类型可以通过实现该接口的方法来表现不同的行为。
type Phone struct {
	phoneNumber int
}

type Email struct {
	email string
}

func (a Phone) SendMsg() string {
	fmt.Printf("%d,手机用户注册成功\n", a.phoneNumber)
	return "success"
}

func (e Email) SendMsg() string {
	fmt.Printf("%s,邮箱用户注册成功\n", e.email)
	return "success"
}

// 基于接口的参数,可以实现传入多中类型(多态),也同时具有约束对象必须实现接口方法的功能
func sendMsg(message Message) {
	message.SendMsg()
}

func Test6(t *testing.T) {
	email := Email{"xxxx@qq.com"}
	phone := Phone{15800000000}

	sendMsg(email)
	sendMsg(phone)
}

输出:

xxxx@qq.com,邮箱用户注册成功
15800000000,手机用户注册成功

底层原理

在 Go 语言中,接口的底层实现是通过一种称为“接口表”(interface table)的机制来实现的。这种机制使得接口能够动态地绑定到具体类型,并支持多态性。

1、空接口的内部结构

type eface struct {
	_type *_type					// 存储类型相关信息
	data  unsafe.Pointer	// 存储数据
}
  1. 类型信息(type information):指向一个描述具体类型的结构体,这个结构体包含了该类型的方法集和其他元数据。
  2. 数据指针(data pointer):指向实际存储的数据。如果这个数据是一个值类型,则直接存储;如果是一个引用或指针,则存储的是地址。

2、非空接口的内部结构

type iface struct {
    tab  *itab         // 指向类型表,它包含了具体类型如何实现该接口的方法信息
    data unsafe.Pointer // 指向实际数据的指针
}

type ITab struct {
	Inter *InterfaceType // 接口信息,如:接口中定义的方法。
	Type  *Type
	Hash  uint32     
	Fun   [1]uintptr 
}

type InterfaceType struct {
	Type
	PkgPath Name
	Methods []Imethod // 接口的方法
}
  1. tab(*itab):这是一个指向 itab 的指针。itab 是一种描述特定类型如何实现某个特定接口的数据结构。它包括了关于该具体类型的方法偏移等信息,以便在运行时能够正确地进行方法调用。是连接特定接口与其实现之间的桥梁。
  2. data(unsafe.Pointer):与 eface 中类似,这是一个不安全指针,用于存储实际的数据地址。如果是值类型,则直接存储其地址;如果是引用或指针,则存储的是该引用或指针所指向内容的地址。
  3. interfacetype:描述一个 Go 接口本身,包括其所属包和需要的方法集。

3、空接口赋值过程

如果在代码中出现其他对象赋值给空接口,其实就是将其他对象相关的值存放到eface的 _typedata中,内部源码:

// The conv and assert functions below do very similar things.
// The convXXX functions are guaranteed by the compiler to succeed.
// The assertXXX functions may fail (either panicking or returning false,
// depending on whether they are 1-result or 2-result).
// The convXXX functions succeed on a nil input, whereas the assertXXX
// functions fail on a nil input.

// convT converts a value of type t, which is pointed to by v, to a pointer that can
// be used as the second word of an interface value.
func convT(t *_type, v unsafe.Pointer) unsafe.Pointer {
	if raceenabled {
		raceReadObjectPC(t, v, getcallerpc(), abi.FuncPCABIInternal(convT))
	}
	if msanenabled {
		msanread(v, t.Size_)
	}
	if asanenabled {
		asanread(v, t.Size_)
	}
  // 使用 mallocgc 为目标类型分配足够大小的堆空间。这个函数会根据传入参数决定是否进行垃圾回收标记。
	x := mallocgc(t.Size_, t, true)
  // 使用 typedmemmove 将源地址中的内容复制到新分配的位置。这一步确保了原始数据被正确地移动到新的位置,并且新位置符合目标类型的信息。
	typedmemmove(t, x, v)
  // 返回新分配空间的地址,这个地址可以作为接口值中的数据部分使用。
	return x
}

当你执行类似以下代码时:

var i interface{}
i = someValue

Go 会进行如下操作:

  1. 获取动态类型:查找并获取 someValue 的动态 _type 信息,这包括其内存布局、方法集等元数据信息。
  2. 设置 eface
    • _type*, 即动态类型信息,保存到 eface._type
    • 将实际数据地址保存到 eface.data

思考

1、接口没有强制性的要求我们实现它的方法,跟java不同,那么它的约束性就小了很多,这种情况下还需要使用接口吗?或者说接口的意义是否低了很多?

Go 的接口实现是隐式的,这与 Java 等语言中的显式实现不同,Go 语言的设计哲学之一是“显式优于隐式”,这意味着 Go 语言倾向于让开发者明确地知道他们正在做什么,而不是依赖于编译器来推断或强制实现某些行为。

接口的隐式实现

在 Go 中,类型不需要显式声明实现某个接口。只要类型实现了接口的所有方法,它就自动实现了该接口。这种隐式实现方式减少了样板代码,使得代码更加简洁。

接口的灵活性

由于接口的隐式实现,Go 语言中的接口非常灵活。你可以在不修改现有类型的情况下,通过实现新的接口来扩展类型的功能。这种灵活性使得 Go 语言在处理多种不同类型的场景时非常强大。

接口的多态性

尽管 Go 语言没有类和继承的概念,但接口提供了一种实现多态的方式。你可以定义一个函数,它接受一个接口类型的参数,然后传递任何实现了该接口的类型。这使得你可以编写更加通用和可重用的代码。

2、在JAVA中,当我们实现某个接口时,idea会提示我们具体需要实现的所有方法,而golang中没有,那么是不是我们就很容易漏掉需要实现的方法

文档和注释

在定义接口时,可以在文档中详细列出所有方法。这样,当你实现接口时,可以参考这些文档来确保没有遗漏任何方法。

代码审查

代码审查是一个很好的实践,可以帮助发现遗漏的方法实现。同事或团队成员可以检查你的代码,确保你已经实现了接口的所有方法。

测试

编写单元测试来测试你的类型是否正确实现了接口。这不仅可以帮助你发现遗漏的方法,还可以确保你的实现按预期工作。

IDE 插件和工具

使用支持 Go 语言的 IDE(如 GoLand)或集成开发环境(如 Visual Studio Code)时,可以使用一些插件和工具来帮助你识别未实现的接口方法。例如,GoLand 有一个功能,可以在你实现接口时自动提示你需要实现的方法。


http://www.kler.cn/a/392804.html

相关文章:

  • Redis - 集群(Cluster)
  • 计算机网络(3)网络拓扑和IP地址,MAC地址,端口地址详解
  • 前端-同源与跨域
  • 假期增设:福祉与负担并存,寻求生活经济平衡之道
  • 【Pikachu】目录遍历实战
  • python购物计算 2024年6月青少年电子学会等级考试 中小学生python编程等级考试一级真题答案解析
  • C++ 优先算法 —— 四数之和(双指针)
  • 二、深度学习_基本概念笔记
  • UVC 输出视频格式修改和windows下数据分析
  • web实验3:虚拟主机基于不同端口、目录、IP、域名访问不同页面
  • Java学生管理系统(GUI和数据库)
  • vue3中查找字典列表中某个元素的值
  • 阅读《当代反无人机系统技术综述》笔记
  • Django 外键引用另一个表中的多个字段
  • Linux文件目录命令
  • 歌尔嵌入式面试题及参考答案
  • Python的装饰器
  • 什么是MVC模式?
  • python爬虫获得淘宝商品类目 API 返回值说明
  • 深入理解 Spark 中的 Shuffle
  • 不同规模的企业需要部署哪种组网?
  • 【Goland】——Gin 框架简介与安装
  • yolo标签自动标注(使用python和yolo方法)
  • 031集——获取外轮廓(只支持线段多段线)(CAD—C#二次开发入门)
  • 海思Hi3516DV300上播放G711U音频文件
  • 【Hadoop】【hdfs】【大数据技术基础】实验三 HDFS 基础编程实验