这是我纯手写的《Go语言入门》,手把手教你入门Go。源码+文章,看了你就会,此处应有掌声!
文章中所有的代码我都放到了github.com/GanZhiXiong/go_learning这个仓库中。在看文章的时候,对照仓库中代码学习效果更佳!
接口本身是调用方和实现方均需遵守的一种协议,大家按照统一的方法命名、参数类型和数量来协调逻辑处理的过程。
接口相当于调用方和实现方的一个中间层,通过中间层约定的接口来协调逻辑处理,解耦了有依赖关系的上下游。
接口是一种较为常见的特性,很多语言都有接口特性。Java、C/C++、C# 语言中的接口都可以多重派生实现接口组合;在苹果的 Objective C 中与接口类似的功能被称为 Protocol,这种叫法比接口更形象、具体。
Go语言不是一种 “传统” 的面向对象编程语言:它里面没有类和继承的概念。
但是Go语言里有非常灵活的接口概念,通过它可以实现很多面向对象的特性。
Go中的接口定义了一组方法的签名。
Go语言的接口实现是隐式
的,无须让实现接口的类型写出实现了哪些接口,直接实现接口中的方法即可。这个设计被称为非侵入式设计
。
与之相反的叫侵入式设计:实现类需要明确声明自己实现了某个接口(如Java:
class Foo implements IFoo
)。
Go的接口设计是非侵入式的,非侵入式体现为:
传统的派生式接口及类关系构建的模式,让类型间拥有强耦合的父子关系。这种关系一般会以“类派生图”的方式进行。经常可以看到大型软件极为复杂的派生树。随着系统的功能不断增加,这棵“派生树”会变得越来越复杂。
非侵入式设计是 Go 语言设计师经过多年的大项目经验总结出来的设计之道。
接口类型是由数个方法组成。
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
er
,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。type writer interface{
Write([]byte) error
}
Go语言提供的很多包中都有接口,例如 io 包中提供的 Writer 接口:
type Writer interface {
Write(p []byte) (n int, err error)
}
类似的,还有将一个对象以字符串形式展现的接口,只要实现了这个接口的类型,在调用 String() 方法时,都可以获得对象对应的字符串。在 fmt 包中定义如下:
type Stringer interface {
String() string
}
Stringer 接口在Go语言中的使用频率非常高,功能类似于 Java 或者 C# 语言里的 ToString 的操作。
在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。
以上两个条件,只要一个不满足就会编译报错。
下面用代码演示接口的实现:
// 定义DataWriter接口
type DataWriter interface {
WriteData(data interface{}) error
// 能否写入
CanWrite() bool
}
// 定义文件结构,用于实现DataWriter
type file struct {
}
func (f *file) CanWrite() bool {
panic("implement me")
}
// 实现DataWriter接口的WriteData方法
// 有些人可能会觉得,下面重复写接口的方法名、参数和返回值很麻烦
// 其实一般IDE都带有快速实现自动生成的功能,比如GoLand在定义结构体的类型名上按下Ctrl+I即可快速实现接口
//func (f *file) WriteDataX(data interface{}) error {
func (f *file) WriteData(data interface{}) error {
fmt.Println("WriteData: ", data)
return nil
}
func TestInterface(t *testing.T) {
f := new(file)
var writer DataWriter
writer = f
writer.WriteData("data")
}
例如:
type Socket struct {
}
// Socket 结构的 Write() 方法实现了 io.Writer 接口:
func (s *Socket) Write(p []byte) (n int, err error) {
return 0, nil
}
// Socket 结构也实现了 io.Closer 接口:
func (s *Socket) Close() error {
return nil
}
// 使用io.Writer的代码, 并不知道Socket和io.Closer的存在
func usingWriter( writer io.Writer){
writer.Write( nil )
}
// 使用io.Closer, 并不知道Socket和io.Writer的存在
func usingCloser( closer io.Closer) {
closer.Close()
}
func TestInterfaceTypeRelations(t *testing.T) {
s := new(Socket)
// usingWriter() 和 usingCloser() 完全独立,互相不知道对方的存在,
// 也不知道自己使用的接口是 Socket 实现的。
usingWriter(s)
usingCloser(s)
t.Log(s)
}
例如下面示例选择将Logger 嵌入到 GameService 能最大程度地避免代码冗余,简化代码结构。
/*
多个类型可以实现相同的接口
*/
type Service interface {
Start() // 启动服务
Log(string) // 日志输出
}
// 日志器
type Logger struct {
}
// 实现Service接口的Log方法
func (l *Logger) Log(s string) {
fmt.Println(s)
}
// 游戏服务
type GameService struct {
// 选择将 Logger 嵌入到 GameService 能最大程度地避免代码冗余,简化代码结构。
Logger
}
func (g *GameService) Start() {
fmt.Println("启动服务成功")
}
func TestGameService(t *testing.T) {
var s Service = new(GameService)
s.Start()
s.Log("hello")
}