go语言中的接口(一)

简介: go语言中的接口

接口

接口是Go语言整个类型系统的基石。

其他语言的接口

Go语言的接口并不是其他语言(C++、Java、C#等)中所提供的接口概念。

在Go语言出现之前,接口主要作为不同组件之间的契约存在。对契约的实现是强制的,你必须声明你的确实现了该接口。为了实现一个接口,你需要从该接口继承:

interface IFoo { 
 void Bar(); 
} 
class Foo implements IFoo { // Java文法
 // ... 
} 
class Foo : public IFoo { // C++文法
 // ... 
} 
IFoo* foo = new Foo; 

即使另外有一个接口IFoo2实现了与IFoo完全一样的接口方法甚至名字也叫IFoo只不过位于不同的名字空间下,编译器也会认为上面的类Foo只实现了IFoo而没有实现IFoo2接口。

这类接口我们称为侵入式接口。“侵入式”的主要表现在于实现类需要明确声明自己实现了某个接口。

设想我们现在要实现一个简单搜索引擎(SE),它需要依赖两个模块,一个是哈希表(HT),一个是HTML分析器(HtmlParser)。搜索引擎的实现者认为,SE对HT的依赖是确定性的,所以不需要在SE和HT之间定义接口,而是直接通过import(或者include)的方式使用HT;而模块SE对HtmlParser的依赖是不确定的,未来可能需要有WordParser、PdfParser等模块来替代HtmlParser,以达到不同的业务要求。为此,他定义了SE和HtmlParser之间的接口,在模块SE中通过接口调用方式间接引用模HtmlParser。

应当注意到,接口的需求方是SE,只有SE才知道接口应该定义成什么样子,但是接口的实现方是HtmlParser。基于模块设计的单向依赖原则,模块HtmlParser实现自身的业务时,不应该关心某个具体使用方的要求。HtmlParser在实现的时候,甚至还不知道未来有一天SE会用上它。期望模块HtmlParser能够知道需求方需要的所有接口,并提前声明实现这些接口是不合理的。

同样的道理发生在SE自己身上。SE并不能够预计未来会有哪些需求方会用到自己,并且实现它们所要求的接口。

这个问题在设计标准库时变得更加突出,比如我们实现了File类,它有下面这些方法:

Read(buf []byte) (n int, err error) 
Write(buf []byte) (n int, err error) 
Seek(off int64, whence int) (pos int64, err error) 
Close() error 

那么,到底是应该定义一个IFile接口,还是应该定义一系列的IReader、IWriter、

ISeeker、ICloser接口,然后让File从它们继承好呢?脱离了实际的用户场景,讨论这两个

非侵入式接口

在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口,例如:

type File struct { 
 // ... 
} 
func (f *File) Read(buf []byte) (n int, err error) 
func (f *File) Write(buf []byte) (n int, err error) 
func (f *File) Seek(off int64, whence int) (pos int64, err error) 
func (f *File) Close() error 

这里我们定义了一个File类,并实现有Read()、Write(),Seek()、Close()等方法。设想我们有如下接口:

type IFile interface { 
 Read(buf []byte) (n int, err error) 
 Write(buf []byte) (n int, err error) 
 Seek(off int64, whence int) (pos int64, err error) 
 Close() error 
} 
type IReader interface { 
 Read(buf []byte) (n int, err error) 
} 
type IWriter interface { 
 Write(buf []byte) (n int, err error) 
} 
type ICloser interface { 
 Close() error 
} 

尽管File类并没有从这些接口继承,甚至可以不知道这些接口的存在,但是File类实现了这些接口,可以进行赋值:

var file1 IFile = new(File) 
var file2 IReader = new(File) 
var file3 IWriter = new(File) 
var file4 ICloser = new(File) 

Go语言的非侵入式接口,看似只是做了很小的文法调整,实则影响深远。其一,Go语言的标准库,再也不需要绘制类库的继承树图。你一定见过不少C++、Java、C# 类库的继承树图。这里给个Java继承树图:http://docs.oracle.com/javase/1.4.2/docs/api/overview-tree.html

在Go中,类的继承树并无意义,你只需要知道这个类实现了哪些方法,每个方法是啥含义就足够了。

其二,实现类的时候,只需要关心自己应该提供哪些方法,不用再纠结接口需要拆得多细才合理。接口由使用方按需定义,而不用事前规划。

其三,不用为了实现一个接口而导入一个包,因为多引用一个外部的包,就意味着更多的耦合。接口由使用方按自身需求来定义,使用方无需关心是否有其他模块定义过类似的接口。

接口赋值

接口赋值在Go语言中分为如下两种情况:

 将对象实例赋值给接口;

 将一个接口赋值给另一个接口。

先讨论将某种类型的对象实例赋值给接口,这要求该对象实例实现了接口要求的所有方法,例如一个Integer类型,如下:

type Integer int
func (a Integer) Less(b Integer) bool { 
 return a < b 
} 
func (a *Integer) Add(b Integer) { 
 *a += b 
} 

相应地,我们定义接口LessAdder,如下:

type LessAdder interface { 
 Less(b Integer) bool
 Add(b Integer) 
} 

现在有个问题:假设我们定义一个Integer类型的对象实例,怎么将其赋值给LessAdder接口呢?应该用下面的语句(1),还是语句(2)呢?

var a Integer = 1 
var b LessAdder = &a ... (1) 
var b LessAdder = a ... (2) 

答案是应该用语句(1)。原因在于,Go语言可以根据下面的函数:

func (a Integer) Less(b Integer) bool 

自动生成一个新的Less()方法:

func (a *Integer) Less(b Integer) bool { 
 return (*a).Less(b) 
} 

这样,类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口。而从另一方面来说,根据

func (a *Integer) Add(b Integer) 

这个函数无法自动生成以下这个成员方法:

func (a Integer) Add(b Integer) { 
 (&a).Add(b) 
} 


go语言中的接口(二)https://developer.aliyun.com/article/1391461

相关文章
|
25天前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
26天前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
1月前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
Go 语言入门指南:切片
|
1月前
|
算法 安全 Go
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
本文探讨了如何利用 Go 语言中的 Bloom Filter 算法提升公司局域网管理系统的性能。Bloom Filter 是一种高效的空间节省型数据结构,适用于快速判断元素是否存在于集合中。文中通过具体代码示例展示了如何在 Go 中实现 Bloom Filter,并应用于局域网的 IP 访问控制,显著提高系统响应速度和安全性。随着网络规模扩大和技术进步,持续优化算法和结合其他安全技术将是企业维持网络竞争力的关键。
51 2
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
|
1月前
|
开发框架 前端开发 Go
eino — 基于go语言的大模型应用开发框架(二)
本文介绍了如何使用Eino框架实现一个基本的LLM(大语言模型)应用。Eino中的`ChatModel`接口提供了与不同大模型服务(如OpenAI、Ollama等)交互的统一方式,支持生成完整响应、流式响应和绑定工具等功能。`Generate`方法用于生成完整的模型响应,`Stream`方法以流式方式返回结果,`BindTools`方法为模型绑定工具。此外,还介绍了通过`Option`模式配置模型参数及模板功能,支持基于前端和用户自定义的角色及Prompt。目前主要聚焦于`ChatModel`的`Generate`方法,后续将继续深入学习。
288 7
|
26天前
|
存储 缓存 监控
企业监控软件中 Go 语言哈希表算法的应用研究与分析
在数字化时代,企业监控软件对企业的稳定运营至关重要。哈希表(散列表)作为高效的数据结构,广泛应用于企业监控中,如设备状态管理、数据分类和缓存机制。Go 语言中的 map 实现了哈希表,能快速处理海量监控数据,确保实时准确反映设备状态,提升系统性能,助力企业实现智能化管理。
34 3
|
1月前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
88 20
|
1月前
|
存储 开发框架 Devops
eino — 基于go语言的大模型应用开发框架(一)
Eino 是一个受开源社区优秀LLM应用开发框架(如LangChain和LlamaIndex)启发的Go语言框架,强调简洁性、可扩展性和可靠性。它提供了易于复用的组件、强大的编排框架、简洁明了的API、最佳实践集合及实用的DevOps工具,支持快速构建和部署LLM应用。Eino不仅兼容多种模型库(如OpenAI、Ollama、Ark),还提供详细的官方文档和活跃的社区支持,便于开发者上手使用。
214 8
|
26天前
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
|
1月前
|
存储 算法 Go
Go语言实战:错误处理和panic_recover之自定义错误类型
本文深入探讨了Go语言中的错误处理和panic/recover机制,涵盖错误处理的基本概念、自定义错误类型的定义、panic和recover的工作原理及应用场景。通过具体代码示例介绍了如何定义自定义错误类型、检查和处理错误值,并使用panic和recover处理运行时错误。文章还讨论了错误处理在实际开发中的应用,如网络编程、文件操作和并发编程,并推荐了一些学习资源。最后展望了未来Go语言在错误处理方面的优化方向。