本文参加了思否技术征文,欢送正在浏览的你也退出。
前言
这是Go常见谬误系列的第15篇:interface应用的常见谬误和最佳实际。
素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi。
本文波及的源代码全副开源在:Go常见谬误源代码,欢送大家关注公众号,及时获取本系列最新更新。
常见谬误和最佳实际
interface是Go语言里的外围性能,然而在日常开发中,常常会呈现interface被乱用的状况,代码适度形象,或者形象不合理,导致代码艰涩难懂。
本文先带大家回顾下interface的重要概念,而后解说应用interface的常见谬误和最佳实际。
interface重要概念回顾
interface外面蕴含了若干个办法,大家能够了解为一个interface代表了一类群体的独特行为。
构造体要实现interface不须要相似implement的关键字,只有该构造体实现了interface里的所有办法即可。
咱们拿Go语言里的io规范库来阐明interface的弱小之处。io规范库蕴含了2个interface:
- io.Reader:示意从某个数据源读数据
- io.Writer:示意写数据到指标地位,比方写到指定文件或者数据库
Figure 2.3 io.Reader reads from a data source and fills a byte slice, whereas io.Writer writes to a target from a byte slice.
io.Reader这个interface里只有一个Read办法:
type Reader interface { Read(p []byte) (n int, err error)}
Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error encountered. Even if Read returns n < len(p), it may use all of p as scratch space during the call. If some data is available but not len(p) bytes, Read conventionally returns what is available instead of waiting for more.
如果某个构造体要实现io.Reader,须要实现Read办法。这个办法要蕴含以下逻辑:
- 入参:承受元素类型为byte的slice作为办法的入参。
- 办法逻辑:把Reader对象里的数据读出来赋值给p。比方Reader对象可能是一个strings.Reader,那调用Read办法就是把string的值赋值给p。
- 返回值:要么返回读到的字节数,要么返回error。
io.Writer这个interface里只有一个Write办法:
type Writer interface { Write(p []byte) (n int, err error)}
Write writes len(p) bytes from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p). Write must not modify the slice data, even temporarily.
如果某个构造体要实现io.Writer,须要实现Write办法。这个办法要蕴含以下逻辑:
- 入参:承受元素类型为byte的slice作为办法的入参。
- 办法逻辑:把p的值写入到Writer对象。比方Writer对象可能是一个os.File类型,那调用Write办法就是把p的值写入到文件里。
- 返回值:要么返回写入的字节数,要么返回error。
这2个函数看起来十分形象,很多Go高级开发者都不太了解,为啥要设计这样2个interface?
试想这样一个场景,假如咱们要实现一个函数,性能是拷贝一个文件的内容到另一个文件。
形式1:这个函数用2个*os.Files作为参数,来从一个文件读内容,写入到另一个文件
func copySourceToDest(source *io.File, dest *io.File) error { // ...}
形式2:应用io.Reader和io.Writer作为参数。因为os.File实现了io.Reader和io.Writer,所以os.File也能够作为上面函数的参数,传参给source和dest。
func copySourceToDest(source io.Reader, dest io.Writer) error { // ...}
办法2的实现会更通用一些,source既能够是文件,也能够是字符串对象(strings.Reader),dest既能够是文件,也能够是其它数据库对象(比方咱们本人实现一个io.Writer,Write办法是把数据写入到数据库)。
在设计interface的时候要思考到简洁性,如果interface里定义的办法很多,那这个interface的形象就会不太好。
援用Go语言设计者Rob Pike在Gopherfest 2015上的技术分享Go Proverbs with Rob Pike中对于interface的阐明:
The bigger the interface, the weaker the abstraction.
当然,咱们也能够把多个interface联合为一个interface,在有些场景下是能够不便代码编写的。
比方io.ReaderWriter就联合了io.Reader和io.Writer的办法。
type ReadWriter interface { Reader Writer}
何时应用interface
上面介绍2个常见的应用interface的场景。
公共行为能够形象为interface
比方下面介绍过的io.Reader和io.Writer就是很好的例子。Go规范库里大量应用interface,感兴趣的能够去查阅源代码。
应用interface让Struct成员变量变为private
比方上面这段代码示例:
package maintype Halloween struct { Day, Month string}func NewHalloween() Halloween { return Halloween { Month: "October", Day: "31" }}func (o Halloween) UK(Year string) string { return o.Day + " " + o.Month + " " + Year}func (o Halloween) US(Year string) string { return o.Month + " " + o.Day + " " + Year}func main() { o := NewHalloween() s_uk := o.UK("2020") s_us := o.US("2020") println(s_uk, s_us)}
变量o能够间接拜访Halloween构造体里的所有成员变量。
有时候咱们可能想做一些限度,不心愿构造体里的成员变量被随便拜访和批改,那就能够借助interface。
type Country interface { UK(string) string US(string) string}func NewHalloween() Country { o := Halloween { Month: "October", Day: "31" } return Country(o)}
咱们定义一个新的interface去实现Halloween的所有办法,而后NewHalloween返回这个interface类型。
那内部调用NewHalloween失去的对象就只能应用Halloween构造体里定义的办法,而不能拜访构造体的成员变量。
乱用Interface的场景
interface在Go代码里常常被乱用,不少C#或者Java开发背景的人在转Go的时候,通常会先把接口类型形象好,再去定义具体的类型。
而后,这并不是Go里举荐的。
Don’t design with interfaces, discover them.
—Rob Pike
正如Rob Pike所说,不要一上来做代码设计的时候就先把interface给定义了。
除非真的有须要,否则是不举荐一开始就在代码里应用interface的。
最佳实际应该是先不要想着interface,因为适度应用interface会让代码艰涩难懂。
咱们应该先依照没有interface的场景去写代码,如果最初发现应用interface能带来额定的益处,再去应用interface。
注意事项
应用interface进行办法调用的时候,有些开发者可能遇到过一些性能问题。
因为程序运行的时候,须要去哈希表数据结构里找到interface的具体实现类型,而后调用该类型的办法。
然而这个开销是很小的,通常不须要关注。
总结
interface是Go语言里一个外围性能,然而使用不当也会导致代码艰涩难懂。
因而,不要在写代码的时候一上来就先写interface。
要先依照没有interface的场景去写代码,如果最初发现应用interface真的能够带来益处再去应用interface。
如果应用interface没有让代码更好,那就不要应用interface,这样会让代码更简洁易懂。
举荐浏览
- Go面试题系列,看看你会几题?
- Go常见谬误第1篇:未知枚举值
- Go常见谬误第2篇:benchmark性能测试的坑
- Go常见谬误第3篇:go指针的性能问题和内存逃逸
- Go常见谬误第4篇:break操作的注意事项
- Go常见谬误第5篇:Go语言Error治理
- Go常见谬误第6篇:slice初始化常犯的谬误
- Go常见谬误第7篇:不应用-race选项做并发竞争检测
- Go常见谬误第8篇:并发编程中Context应用常见谬误
- Go常见谬误第9篇:应用文件名称作为函数输出
- Go常见谬误第10篇:Goroutine和循环变量一起应用的坑
- Go常见谬误第11篇:意外的变量遮蔽(variable shadowing)
- Go常见谬误第12篇:如何破解箭头型代码
- Go常见谬误第13篇:init函数的常见谬误和最佳实际
- Go常见谬误第14篇:适度应用getter和setter办法
开源地址
文章和示例代码开源在GitHub: Go语言高级、中级和高级教程。
公众号:coding进阶。关注公众号能够获取最新Go面试题和技术栈。
集体网站:Jincheng's Blog。
知乎:无忌。
福利
我为大家整顿了一份后端开发学习材料礼包,蕴含编程语言入门到进阶常识(Go、C++、Python)、后端开发技术栈、面试题等。
关注公众号「coding进阶」,发送音讯 backend 支付材料礼包,这份材料会不定期更新,退出我感觉有价值的材料。
发送音讯「进群」,和同行一起交流学习,答疑解惑。
References
- https://livebook.manning.com/...
- https://github.com/jincheng9/...
- https://bbs.huaweicloud.com/b...