接口

接口是定义了合约但并没有实现的类型。举个例子:

type Logger interface {
  Log(message string)
}

那这样做有什么作用呢?其实,接口有助于将代码与特定的实现进行分离。例如,我们可能有各种类型的日志记录器:

type SqlLogger struct { ... }
type ConsoleLogger struct { ... }
type FileLogger struct { ... }

针对接口而不是具体实现的编程会使我们很轻松的修改(或者测试)任何代码都不会产生影响。

你会怎么用?就像任何其它类型一样,它结构可以这样:

type Server struct {
  logger Logger
}

或者是一个函数参数(或者返回值):

func process(logger Logger) {
  logger.Log("hello!")
}

在像 C# 或者 Java 这类语言中,当类实现接口时,我们必须显式的:

public class ConsoleLogger : Logger {
  public void Logger(message string) {
    Console.WriteLine(message)
  }
}

在 Go 中,下面的情况是隐式发生的。如果你的结构体有一个函数名为 Log 且它有一个 string 类型的参数并没有返回值,那么这个结构体被视为 Logger 。这减少了使用接口的冗长:

type ConsoleLogger struct {}
func (l ConsoleLogger) Log(message string) {
  fmt.Println(message)
}

Go 倾向于使用专注的接口。Go 的标准库基本上由接口组成。像 io 包有一些常用的接口诸如 io.Readerio.Writerio.Closer 等。如果你编写的函数只需要一个能调用 Close() 的参数,那么你应该接受一个 io.Closer 而不是像 io 这样的父类型。
接口可以成为其他接口的一部分,也就是说接口也可以与其他接口组成新的接口。例如, io.ReadCLoser 的接口是由 io.Reader 接口和 io.Closer 接口组成的。

最后,接口通常用于避免循环导入。由于它们没有具体的实现,因此它们的依赖是有限的。

热门教程

最新教程