写在前面:若有侵权,请发邮件by.su@qq.com告知。
转载者告知:如果本文被转载,但凡涉及到侵权相关事宜,转载者需负责。请知悉!
本文永久更新地址:https://my.oschina.net/bysu/blog/3036779
【若要到岸,请摇船:开源中国 不最醉不龟归】
很多年前学习了一下goland,但是没有怎么开始就放下了,这次贴了心学到底。看了一下,觉得goland比较重要的部分就是:1.函数;2.结构体;3.接口;4.并发。
每次要学什么东西之前,我就会狂搜罗一大堆相关的书籍及教程,然后从中挑选一两本或者各教程中某部分来看。这次也不例外,看了很多本关于goland的书,觉得还是《Go语言从入门到进阶实战》比较不错,比较适合我这种菜鸡(连菜鸟都不算)的人,并且里面的例子确实挺好的。好了,废话不多说,下面的内容就是看《Go语言从入门到进阶实战》这本书接口这部分的笔(抄)记(袭),感兴趣的可以买来学习一下。
---------------------------------------
Go语言的接口设计是非侵入式的,接口编写者无须知道接口被哪些类型实现,而接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪个接口。
1.声明接口
接口是双方约定的一种合作协议。接口实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现细节。接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式、类型及结构。
1.1 接口声明的格式
每个接口类型由一个或多个方法组成。接口的形式代码如下:
type 接口类型名 interface{
方法名1(参数列表1) 返回值列表1
方法名2(参数列表2) 返回值列表2
...
}
type define_interfacer interface {
inter_face_string(str string)string
inter_face_int(i int)int
}
1.2.开发中常见的接口及写法
Go语言提供的很多包中都有接口,如io包中提供的Writer接口:
type Writer interface {
Write(p []byte) (n int, err error)
}
这个接口可以调用Writer()方法写入一个字节数组([]byte),返回值告知写入字节数和可能发生的错误。
类似的,还有将一个对象以字符串形式展现的接口,只要实现了这个接口的类型,在调用String()方法时,都可以获得对应的字符串。在fmt包中定义如下:
type Stringer interface {
String() string
}
String借口在Go语言中的使用频率非常高,功能类似于java语言中的ToString的操作。
Go语言的每个接口中的方法数量不会很多。Go语言希望通过一个接口精准描述它自己的功能,而通过多个接口的嵌入和组合的方式将简单的接口扩展为复杂的接口。
2.实现接口的条件
接口定义后,需要实现接口,调用方才能正确编译通过并使用接口。接口的实现需要遵循两条规则才能让接口可用。
2.1 接口被实现的条件一:接口的方法与实现接口的类型方法格式一致
在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。
为了抽象数据写入的过程,定义DataWriter接口来描述数据写入需要实现的方法,接口中的WriteData()方法表示将数据写入,写入方无须关心写入到哪里。实现接口的类型实现WriterData方法时,会具体编写将数据写入到什么结构中。这里使用file结构体实现DataWriter接口的WriteData方法,方法内部只是打印一个日志,表示有数据写入,详细实现过程参考如下代码:
程序执行结果:
当类型无法实现接口时,编译器会报错,下面列出常见的几种接口无法实现的错误。
2.1.1函数名不一致导致的报错
如下图
2.1.2.实现接口的方法签名不一致导致的报错
如下图:
报错信息:
2.2 接口被实现的条件二:接口中所有方法均被实现
当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。在接口DataWriter中新增CanWrite方法,见下图11行。
重新编译后,报错信息如下:
Go语言的接口实现是隐式的,无须让实现接口的类型写出实现了哪些接口。这个设计被称为非侵入式设计。
实现者在编写方法时,无法预测未来哪些方法会变为接口。提示:对于Go语言来说,非侵入式设计让实现者的所有类型均是平行的、组合的。如何组合则留到使用者编译时再确认。使用Go语言时,开发者唯一需要关注的是“我需要什么”,以及“我能实现什么?”。
3.理解类型与接口的关系
类型和接口直接有一对多和多对一的关系。
3.1 一个类型可以实现多个接口
一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
Socket能够同时读取和写入数据,这个特性与文件类似。因此,开发中把文件和Socket都具备的读写特性抽象为独立的读写器概念。
Socket和文件一样,在使用完毕后,也需要对资源进行释放。
把Socket能够写入数据和需要关闭的特性使用接口来描述,请参考下面的代码:
使用Socket实现的Writer接口的代码,无须了解Writer接口的实现者是否具备Closer接口的特性。同样,使用Closer接口的代码也并不知道Socket已经实现了Writer接口。
在代码中使用Socket结构实现的Writer接口和Closer接口代码如下:
3.2 多个类型可以实现相同的接口
一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的。
Socket接口定义了两个方法:一个是开启服务的方法(Start()),一个是输出日志的方法(Log()),使用GameService结构体来实现Service,GameService自己的结构只能实现Start()方法,而Service接口中的Log()方法已经被一个能输出日志的日志器(Logger)实现了,无须再进行GameService封装,或者重新实现一遍。所以,选择将Logger嵌入到GameService能最大程度地避免代码冗余,简化代码结构。
2.4 示例:便于扩展输出方式的日志系统
日志可以用于查看和分析应用程序的运行状态。日志一般可以支持输出多种格式,如命令行、文件、网络等。
本例将搭建一个支持多种写入器的日志系统,可以自由扩展多种日志写入设备。
2.4.1.日志对外接口
本例中定义一个日志写入器接口(LogWriter),要求写入设备必须遵守这个接口的协议才能被日志器(Logger)注册。日志器有一个写入器的注册方法(Logger的RegisterWriter()方法)。
日志器还有一个Log()方法,进行日志的输出,这个函数会将日志写入到所有已经注册的日志写入器(LogWriter)中,详细代码如下:
2.4.2 文件写入器
文件写入器(fileWriter)是众多日志写入器(LogWriter)中的一种。文件写入器的功能是根据一个文件名创建日志文件(fileWriter的SetFile方法)。在有日志写入时,将日志写入文件中。
一个完备的文件写入器会提供多种写入文件的模式,例子中使用的模式是将日志添加到日志文件的尾部。随着文件越来越大,文件的访问效率和查看便利性也会大大降低。此时,就需要另外一种写入模式:滚动写入文件。
滚动写入文件模式也是将日志添加到文件的尾部,但当文件达到设定的期望大小时,会自动开启一个新的文件继续写入文件,最终将获得多个日志文件。日志文件名不仅可以按照文件大小进行分割,还可以按照日期范围进行分割。在到达设定的日期范围,如每天、每小时的周期范围时,日志器会自动创建新的日志文件。这种日志文件创建方法也能方便开发者按日志查看日志。
2.4.3 命令行写入器
在UNIX的思想中,一切皆文件。命令行在go中也是一种文件,os.Stdout对应标准输出,一般表示屏幕,也就是命令行,也可以被重定向为打印或者磁盘文件;os.Stderr对应标准错误输出,一般将错误输出到日志中,不过大多数情况,os.Stdout会与os.Stderr合并输出;os.Stdin对应标准输入,一般表示键盘。os.Stdout、os.Stderr、os.Stdin都是*os.File类型,和文件一样实现了io.Writer接口的Writer()方法。
除了命令行写入器(consoleWriter)和文件写入器(fileWriter),读者还可以自行使用net包中的Socket封装实现网络写入器SocketWriter,让日志可以写入远程的服务器中或者可以跨进程进行日志保存和分析。
2.4.4 使用日志
在程序中使用日志器(Logger),为日志器添加输出设备(fileWriter、consoleWriter等)。这些设备中有一部分需要一些参数设定,如文件日志写入器需要提供文件名(fileWriter的SetFile()方法)。