在 Golang 中使用多个接口
来源:Golang技术栈
时间:2023-04-13 14:33:06 369浏览 收藏
小伙伴们有没有觉得学习Golang很有意思?有意思就对了!今天就给大家带来《在 Golang 中使用多个接口》,以下内容将会涉及到golang,若是在学习中对其中部分知识点有疑问,或许看了本文就能帮到你!
问题内容
我正在学习 Golang,作为使用接口的练习,我正在构建一个玩具程序。我在尝试使用“应该实现”两个接口的类型时遇到了一些问题——在 C++ 和 Java 中解决这个问题的一种方法是使用继承(还有其他技术,但我认为这是最常见的)。由于我在 Golang 中缺乏该机制,因此我不确定如何进行。下面是代码:
var ( faces = []string{"Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King"} suits = []string{"Hearts", "Diamonds", "Spades", "Clubs"} ) type Card interface { GetFace() string GetSuit() string } type card struct { cardNum int face string suit string } func NewCard(num int) Card { newCard := card{ cardNum: num, face: faces[num%len(faces)], suit: suits[num/len(faces)], } return &newCard } func (c *card) GetFace() string { return c.face } func (c *card) GetSuit() string { return c.suit } func (c *card) String() string { return fmt.Sprintf("%s%s ", c.GetFace(), c.GetSuit()) }
我想要达到的目标:
- 我想隐藏我的结构类型,只导出接口,以便代码的客户端只使用 “卡” 接口
- 我想要结构的字符串表示,因此使用 “String()” 方法实现接口,以便能够在我的结构的实例化上调用 “fmt.Println()”
当我尝试通过“卡”界面使用新卡并尝试获取字符串表示时,问题就出现了。我无法将接口作为 “String()” 方法实现的参数传递,因为存在与核心语言级别的接口可寻址性相关的编译器错误(仍在挖掘该文档)。一个非常简单的测试示例暴露了这个问题:
func TestString(t *testing.T) { card := NewCard(0) assert.EqualValues(t, "AceHearts ", card.String(), " newly created card's string repr should be 'AceHearts '") }
编译器有充分的理由告诉我 "card.String undefined (type card has no field or method string)" 。我可以将 “String()” 方法添加到我的 “Card” 接口中,但我认为这并不干净:我有其他实体使用相同的模型实现,我必须在任何地方添加冗余;该方法已经有一个接口。
对于我遇到的上述问题,什么是好的解决方案?
编辑:( 解决一些非常好的评论)
- 我不希望有另一个 Card 接口的实现;我不确定我是否理解我为什么要这样做,那就是改变界面
- 我想让 Card 接口隐藏实现细节,并让客户端针对接口而不是具体类型进行编程
- 我希望始终可以访问“card struct”实例化的所有客户端(包括通过 Card 接口实例化的客户端)的 String() 接口。我对只使用 String 接口的客户不感兴趣。在其他一些语言中,这可以通过实现两个接口来实现 - 多重继承。我不是说这是好是错,或者试图就此展开辩论,我只是在陈述一个事实!
- 我的目的是找出该语言是否有任何机制可以同时满足这些要求。如果这是不可能的,或者从设计的角度来看问题应该以不同的方式解决,那么我已经准备好接受教育了
- 类型断言非常冗长和明确,并且会暴露实现细节 - 它们有它们的位置,但我认为它们不适合我的情况
正确答案
我应该先回顾一些前言:
- Go 中的接口与其他语言中的接口不同。你不应该假设来自其他语言的每个想法都应该自动转移。他们中的很多人都没有。
- Go 既没有类也没有对象。
- Go 不是 Java,Go 也不是 C++。它的类型系统与那些语言有显着和有意义的不同。
从你的问题:
我想让 Card 接口隐藏实现细节,并让客户端针对接口而不是具体类型进行编程
这是你其他问题的根源。
正如评论中提到的,我在多个其他包中看到了这一点,并将其视为一种特别讨厌的反模式。首先,我将解释为什么这种模式本质上是“反”的。
- 首先,也是最相关的,你的例子证明了这一点。您采用了这种模式,它导致了不良影响。正如 mkopriva 所指出的,它产生了一个你 必须 解决的矛盾。
- 接口的这种用法与其预期用途相反,并且您不会通过这样做获得任何好处。
接口是 Go
的多态机制。在参数中使用接口使您的代码更加通用。想想无处不在的io.Reader
和io.Writer
。它们是出色的接口示例。它们是您可以将两个看似无关的库修补在一起并让它们正常工作的原因。例如,您可以记录到
stderr,或记录到磁盘文件,或记录到 http
响应。这些中的每一个都以完全相同的方式工作,因为log.New需要一个io.Writer
参数,并且磁盘文件、stderr
和 http 响应编写器都实现了io.Writer
.
仅仅使用接口来“隐藏实现细节”(我稍后会解释为什么这点失败),不会为您的代码增加任何灵活性。如果有的话,那就是滥用接口,将接口用于他们不打算完成的任务。
点/对位
- “通过确保隐藏所有细节,隐藏我的实现提供了更好的封装和安全性。”
- 您没有实现更大的封装或安全性。通过使结构字段不导出(小写),您已经防止包的任何客户端弄乱结构的内部。包的客户端只能访问您导出的字段或方法。导出结构并隐藏每个字段并没有错。
- “结构值是肮脏和原始的,我对传递它们感觉不好。”
- 然后不要传递结构,传递指向结构的指针。这就是你已经在这里做的事情。传递结构本身并没有错。如果您的类型表现得像一个可变对象,那么指向 struct 的指针可能是合适的。如果您的类型表现得更像一个不可变的数据点,那么 struct 可能是合适的。
package.Struct
“如果我的包导出,但客户必须始终使用,这不是很混乱*package.Struct
吗?如果他们犯了错误怎么办?复制我的结构值是不安全的;事情会破坏!”
- 为了防止出现问题,您实际上需要做的就是确保您的包 只返回
*package.Struct
值。这就是你已经在这里做的事情。绝大多数时候,人们会使用 short assignment:=
,所以他们不必担心类型是否正确。如果他们确实手动设置了类型,并且package.Struct
意外选择了,那么他们在尝试为其分配 a 时会出现编译错误*package.Struct
。
- “它有助于将客户端代码与包代码解耦”
- 可能是。但是,除非您有一个现实的期望,即您有多个这种类型的现有实现,那么这是一种过早优化的形式(是的,它确实会产生后果)。即使您的接口 确实有多个实现,这仍然不是您应该实际 返回 该接口的值的好理由。大多数情况下,只返回具体类型仍然更合适。要了解我的意思,请查看
image
标准库中的包。
什么时候真正有用?
制作过早的接口并 返回它 可能 对客户有帮助的主要现实案例是:
- 你的包引入了接口的第二个实现
- AND 客户端已在其 函数或类型中静态且显式(未
:=
)使用此数据类型 __ - AND 客户也希望将这些类型或功能重用于新的实现。
- AND 客户端已在其 函数或类型中静态且显式(未
请注意,即使您没有返回过早的接口,这也不会是一个破坏性的 API 更改,因为您只是 添加 了一个新的类型和构造函数。
如果您决定只 声明 这个过早的接口,并且仍然返回具体类型(如image
包中所做的那样),那么客户可能需要做的所有补救措施就是花几分钟使用他们的
IDE 的重构工具来*package.Struct
替换package.Interface
.
它严重阻碍了包文档的可用性
Go 拥有一个名为 Godoc 的有用工具。Godoc 自动从源代码生成包的文档。当你在你的包中导出一个类型时,Godoc 会向你展示一些有用的东西:
- 类型、该类型的所有导出方法以及返回该类型的所有函数都组织在 doc 索引中。
- 类型及其每个方法在显示签名的页面中都有一个专用部分,以及解释其用法的注释。
一旦你将你的结构气泡包装到一个接口中,你的 Godoc 表示就会受到伤害。您的类型的方法不再显示在包索引中,因此包索引不再是包的准确概述,因为它缺少很多关键信息。此外,每种方法在页面上不再有自己的专用空间,这使得文档更难找到和阅读。最后也意味着你不再能够点击文档页面上的方法名来查看源代码。这也不是巧合,在许多采用这种模式的包中,这些不强调的方法通常没有文档注释,即使包的其余部分都有很好的文档说明。
在野外
https://pkg.go.dev/github.com/zserge/lorca
https://pkg.go.dev/github.com/googollee/go- socket.io
在这两种情况下,我们都看到了误导性的包概述,以及大多数接口方法未记录在案。
(请注意,我对这些开发人员中的任何一个都没有任何反对意见;显然每个包都有它的缺点,这些示例都是精心挑选的。我也不是说他们没有理由使用这种模式,只是他们的包文档受到了阻碍)
标准库中的示例
如果您对接口“打算如何使用”感到好奇,我建议您查看标准库的文档并注意接口的声明、作为参数和返回的位置。
https://golang.org/pkg/net/http/
https://golang.org/pkg/crypto/
这是我所知道的唯一一个可以与“接口隐藏”模式相媲美的标准库示例。在这种情况下,reflect
是一个非常复杂的包,内部有几种实现reflect.Type
。另请注意,在这种情况下,即使有必要,也不会有人对此感到高兴,因为对客户的唯一真正影响是文档更加混乱。
https://golang.org/pkg/reflect/#Type
tl;博士
这种模式会损害您的文档,同时在此过程中不会完成任何事情,除了您可能会在非常特定的情况下稍微加快客户使用这种类型的并行实现的速度,您将来可能会或可能不会引入。
这些界面设计原则是为了客户的利益,对吧?设身处地为客户着想,问:我真正得到了什么?
到这里,我们也就讲完了《在 Golang 中使用多个接口》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于golang的知识点!
-
439 收藏
-
262 收藏
-
193 收藏
-
188 收藏
-
500 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
477 收藏
-
486 收藏
-
439 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习