news 2026/4/23 16:09:15

Go进阶之方法集合接口实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go进阶之方法集合接口实现

自定义类型的方法和接口都是Go语言中的概念.并且他们之间存在千丝万缕的关系.示例:

package main type Interface interface { M1() M2() } type T struct{} func (t T) M1() {} func (t *T) M2() {} func main() { var t T var pt *T var i Interface i = t i = pt }

上边的例子没有通过编译器检查.编译器给出的信息是:不能使用变量t给接口类型变量

i赋值.因为t没有实现Interface接口方法集合中的M2()方法.下面会慢慢解除疑惑.

1.方法集合:

上一篇文章说过receiver类型除了考量是否需要对类型实例进行修改 类型实例复制

导致的性能损耗之外.还有一个因素就是类型是否需要实现某个接口类型.

Go语言的一个创新是.自定义类型与接口之间实现关系是松耦合的.如果某个自定义类

型T的方法集合是某个接口类型的方法集合的超集.那么就说类型T实现了该接口.并且

该类型T的变量可以被赋值给该接口类型的变量.即方法集合决定接口实现.

方法集合是Go语言中一个重要的概念.在为接口类型变量赋值 使用结构体嵌入 接口

嵌入 类型别名和方法表达式等时都会用到方法集合.像胶水一样自定义类型与接口隐

式的粘结在一起.

要判断一个自定义类型是否实现了某接口类型.首先要识别出自定义类型的方法集合

和接口类型的方法集合.有时候并不明显.可以通过一个工具函数方便的输出自定义类

型或接口的方法集合.

工具函数:
func DumpMethodSet(i interface{}) { v := reflect.TypeOf(i) elemTyp := v.Elem() n := elemTyp.NumMethod() if n == 0 { fmt.Printf("%s 方法个数为0", elemTyp) return } fmt.Printf("%s 集合\n", elemTyp) for i := 0; i < n; i++ { fmt.Println("-", elemTyp.Method(i).Name) } fmt.Printf("\n") }
把开头的代码调用如上工具类:
package main import ( "fmt" "reflect" ) type Interface interface { M1() M2() } type T struct{} func (t T) M1() {} func (t *T) M2() {} func main() { var t T var pt *T DumpMethodSet(&t) DumpMethodSet(&pt) DumpMethodSet((*Interface)(nil)) } func DumpMethodSet(i interface{}) { v := reflect.TypeOf(i) elemTyp := v.Elem() n := elemTyp.NumMethod() if n == 0 { fmt.Printf("%s 方法个数为0", elemTyp) return } fmt.Printf("%s 集合\n", elemTyp) for i := 0; i < n; i++ { fmt.Println("-", elemTyp.Method(i).Name) } fmt.Printf("\n") }
执行结果:

从执行结果中可以看出.var t T的方法集合中并不是Interface的超集.所以无法进行

赋值.

2.类型嵌入与方法集合:

Go语言的设计哲学之一是偏好组合.Go语言支持用组合的思想来实现一些面向对象

领域经典的机制.比如继承.

与接口类型和结构体类型相关的类型嵌入有三种组合.在接口类型中嵌入接口类型. 在

结构体类型中嵌入接口类型. 在结构体类型中嵌入结构体类型.

2.1在接口类型中嵌入接口类型:

按Go语言惯例.接口类型中仅包含少量方法.并且常常仅有一个方法.通过在接口类型

中嵌入其他接口类型实现接口的组合.比如io包中的ReadWriter

ReadWriteCloser 等接口类型就是通过嵌入Reader Writer Closer三个基本接口

类型形成的.源码如下:

type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error }

上面为三个基本接口.

type ReadWriter interface { Reader Writer } type ReadCloser interface { Reader Closer } type WriteCloser interface { Writer Closer } type ReadWriteCloser interface { Reader Writer Closer }

再来看看通过嵌入接口类型后新接口类型的方法集合,以Go标准库中的几个接口为例

子.

示例如下:
func main() { DumpMethodSet((*io.Writer)(nil)) DumpMethodSet((*io.Reader)(nil)) DumpMethodSet((*io.Closer)(nil)) DumpMethodSet((*io.ReadWriter)(nil)) DumpMethodSet((*io.ReadWriteCloser)(nil)) }
执行结果:

通过输出结果可知.通过嵌入接口类型而创建的新接口类型的方法集合包括了被嵌入

接口类型的方法集合.

在Go1.14之前版本有一个约束.被嵌入接口类型的方法集合不能有交集.同时被嵌入

的接口类型的方法集合中的方法不能与新接口中其他方法同名.

type Interface1 interface { M1() } type Interface2 interface { M1() M2() } type Interface3 interface { Interface1 Interface2 } type Interface4 interface { Interface2 M2() }

Go1.14版本开始去掉了该约束.

2.2在结构体类型中嵌入接口类型:

在结构体类型中嵌入接口类型后.该结构体类型的方法集合中将包含被嵌入接口类型

的方法集合.

示例:
package main import ( "fmt" "reflect" ) type Interface interface { M1() M2() } type T struct { Interface } func (T) M3() { } func main() { DumpMethodSet((*Interface)(nil)) var t T var pt *T DumpMethodSet(&t) DumpMethodSet(&pt) }
运行结果:

输出的结果与预期一致.如果当结构体类型中嵌入多个接口类型且这些接口类型的方

法集合存在交集时.结果就会出现不一样.

Go选择方法次序:

1).优先选择结构体自身实现的方法.

2).如果结构体自身并未实现.那么将查找结构体中的嵌入接口类型的方法集合中是否

有方法.如果有.则提升为结构体方法.

示例如下:
package main import ( "fmt" "reflect" ) type Interface interface { M1() M2() } type T struct { Interface } func (T) M1() { fmt.Println("struct M1") } type S struct{} func (S) M1() { fmt.Println("s M1") } func (S) M2() { fmt.Println("s M2") } func main() { var t = T{ Interface: S{}, } t.M1() t.M2() }
输出结果:

如果结构体嵌入了多个接口类型且这些接口类型的方法集合存在交集.Go编译器将会

报错.除非结构体自己实现了交集中所有的方法.

示例如下:
package main import ( "fmt" "reflect" ) type Interface interface { M1() M2() M3() } type Interface1 interface { M1() M2() M4() } type T struct { Interface Interface1 } func main() { t := new(T) t.M1() t.M2() }

可以看到编译器给出错误提示.编译器在t.M1和t.M2出现分歧.不知道该选择哪个.

修证示例:
package main import ( "fmt" "reflect" ) type Interface interface { M1() M2() M3() } type Interface1 interface { M1() M2() M4() } type T struct { Interface Interface1 } func (t *T) M1() { } func (t *T) M2() { } func main() { t := new(T) t.M1() t.M2() }

结构体类型在嵌入某个接口类型的同时.也实现了这个接口.这一特性在单元测试中特

别有用.(后续单元测试的时候在详情举例).

2.3在结构体类型中嵌入结构体类型:

在结构体中嵌入结构体提供了一种实现继承的手段.外部的结构体类型T可以继承嵌入

的结构体类型的所有方法.无论是T类型的变量实例还是*T类型变量实例.都可以调用

所有继承的方法.

示例:
package main import ( "fmt" "reflect" ) type T1 struct { } type T2 struct { } func (t T1) T1M1() { fmt.Println("T1M1") } func (t T1) T1M2() { fmt.Println("T1M2") } func (t *T1) T1M3() { fmt.Println("T1M3") } func (t T2) T2M1() { fmt.Println("T2M1") } func (t T2) T2M2() { fmt.Println("T2M2") } func (t *T2) T2M3() { fmt.Println("T2M3") } type T struct { T1 T2 } func main() { t := T{ T1: T1{}, T2: T2{}, } fmt.Println("call method through t") t.T1M1() t.T1M2() t.T1M3() t.T2M1() t.T2M2() t.T2M3() fmt.Println("\ncall method through pt") pt := &t pt.T1M1() pt.T1M2() pt.T1M3() pt.T2M1() pt.T2M2() pt.T2M3() var t1 T1 var pt1 *T1 DumpMethodSet(&t1) DumpMethodSet(&pt1) var t2 T2 var pt2 *T2 DumpMethodSet(&t2) DumpMethodSet(&pt2) } func DumpMethodSet(i interface{}) { v := reflect.TypeOf(i) elemTyp := v.Elem() n := elemTyp.NumMethod() if n == 0 { fmt.Printf("%s 方法个数为0", elemTyp) return } fmt.Printf("%s 集合\n", elemTyp) for i := 0; i < n; i++ { fmt.Println("-", elemTyp.Method(i).Name) } fmt.Printf("\n") }
执行结果:

从结果可以看出.

T类型的方法集合=T1的方法集合+*T2的方法集合.

*T类型的集合=*T1的方法集合+*T2的方法集合.

3.defined类型的方法集合.

Go语言支持基于已有的类型创建新类型.示例如下:

type MyInterface T1 type MyInterface2 T2

已有的类型(比如上面的T1 T2)被称为underlying类型.新类型被称为defined类型.

新定义的类型和原类型方法集合会有什么不同.

示例如下:
package main import ( "fmt" "reflect" ) type T struct{} func (t T) M1() { } func (t *T) M2() { } type Interface interface { M1() M2() } type T1 T type Interface1 Interface func main() { var t T var pt *T var t1 T1 var pt1 *T1 DumpMethodSet(&t) DumpMethodSet(&pt) DumpMethodSet(&t1) DumpMethodSet(&pt1) DumpMethodSet((*Interface)(nil)) DumpMethodSet((*Interface1)(nil)) } func DumpMethodSet(i interface{}) { v := reflect.TypeOf(i) elemTyp := v.Elem() n := elemTyp.NumMethod() if n == 0 { fmt.Printf("%s 方法个数为0", elemTyp) return } fmt.Printf("%s 集合\n", elemTyp) for i := 0; i < n; i++ { fmt.Println("-", elemTyp.Method(i).Name) } fmt.Printf("\n") }
执行结果:

从结果来看.Go对于分别基于接口类型和自定义非接口类型创建的defined类型给出

了不一样的结果.

基于接口类型创建的defined类型与原接口类型的方法集合是一致的.

而基于自定义非接口类型创建的defiend类型并没有继承原类型的方法集合.新的

dfiend类型的方法集合是空的.

4.类型别名的方法集合:

类型别名与原类型几乎是等价的.但是在方法集合上与原类型是否有区别呢.

示例:
package main import ( "fmt" "reflect" ) type T struct { } func (T) M1() { fmt.Println("M1") } func (*T) M2() { fmt.Println("M2") } type Interface interface { M1() M2() } type T1 = T type Interface1 = Interface func main() { var t T var pt *T var t1 T1 var pt1 *T1 DumpMethodSet(&t) DumpMethodSet(&pt) DumpMethodSet(&t1) DumpMethodSet(&pt1) DumpMethodSet((*Interface)(nil)) DumpMethodSet((*Interface1)(nil)) } func DumpMethodSet(i interface{}) { v := reflect.TypeOf(i) elemTyp := v.Elem() n := elemTyp.NumMethod() if n == 0 { fmt.Printf("%s 方法个数为0", elemTyp) return } fmt.Printf("%s 集合\n", elemTyp) for i := 0; i < n; i++ { fmt.Println("-", elemTyp.Method(i).Name) } fmt.Printf("\n") }
执行结果:

从输出结果可以看出.函数甚至都无法识别出类型别名.无论类型别名还是原类型输出

的都是一样的方法集合.由此可以知道.无论别名与原类型拥有完全相同的方法集合.无

论原类型是接口类型还是非接口类型.

让风吹过我耳廓.烦恼都吹破.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 11:27:42

ESLyric歌词增强工具完全指南

ESLyric歌词增强工具完全指南 【免费下载链接】ESLyric-LyricsSource Advanced lyrics source for ESLyric in foobar2000 项目地址: https://gitcode.com/gh_mirrors/es/ESLyric-LyricsSource 1.突破传统&#xff1a;如何让播放器拥有专业音乐平台的歌词体验&#xff1…

作者头像 李华
网站建设 2026/4/23 12:47:29

Chandra多场景:HR部门用Chandra自动生成面试问题、岗位JD与录用通知书

Chandra多场景&#xff1a;HR部门用Chandra自动生成面试问题、岗位JD与录用通知书 1. 引言&#xff1a;AI如何改变HR日常工作 想象一下这样的场景&#xff1a;HR小李正在为下周的招聘会做准备。她需要为10个不同岗位设计面试问题、编写岗位描述&#xff0c;还要准备录用通知书…

作者头像 李华
网站建设 2026/4/23 14:52:11

YOLOv9数据准备指南,YOLO格式标注不再难

YOLOv9数据准备指南&#xff0c;YOLO格式标注不再难 在目标检测项目中&#xff0c;真正卡住80%新手的从来不是模型结构或训练技巧&#xff0c;而是数据准备——尤其是YOLO格式的数据组织与标注。你是否也经历过&#xff1a;标注工具导出的文件路径错乱、类别ID对不上、图片和标…

作者头像 李华
网站建设 2026/4/23 12:59:30

零基础精通LDBlockShow:从入门到实战的完整指南

零基础精通LDBlockShow&#xff1a;从入门到实战的完整指南 【免费下载链接】LDBlockShow LDBlockShow: a fast and convenient tool for visualizing linkage disequilibrium and haplotype blocks based on VCF files 项目地址: https://gitcode.com/gh_mirrors/ld/LDBlock…

作者头像 李华
网站建设 2026/4/23 13:23:23

AI知识库构建入门必看:GTE-Chinese-Large+SeqGPT-560m保姆级教程

AI知识库构建入门必看&#xff1a;GTE-Chinese-LargeSeqGPT-560m保姆级教程 1. 项目概述与核心价值 AI知识库正在改变我们获取和处理信息的方式。本教程将带你从零开始&#xff0c;使用GTE-Chinese-Large语义向量模型和SeqGPT-560m轻量化文本生成模型&#xff0c;构建一个智能…

作者头像 李华
网站建设 2026/4/23 14:40:37

SenseVoice Small语音识别实战案例:播客节目逐字稿生成全流程

SenseVoice Small语音识别实战案例&#xff1a;播客节目逐字稿生成全流程 1. 为什么选SenseVoice Small做播客转写&#xff1f; 你有没有试过听一档45分钟的深度播客&#xff0c;边听边记重点&#xff1f;或者想把嘉宾访谈整理成公众号推文&#xff0c;结果光是听写就花了三小…

作者头像 李华