Go基础:接口

738 阅读4分钟

本文正在参加金石计划

「前言」

什么是接口

接口是一种标准化的约定或规范,它规定了两个个体之间如何进行通信和交互。

假设你要与一个陌生人交谈,你可能需要遵守一些基本的礼仪规范,比如问候、介绍自己、交换名片等等。这些规范可以帮助你和陌生人之间建立信任和合作关系,同时也确保了交流的顺畅和准确。

在实际编程中,实现一个接口通常需要实现接口定义的所有方法,以确保对象可以被正确使用和调用。这也意味着接口可以被用来强制规范对象的行为,以确保它们符合特定的要求和标准。

接口是一种抽象的工具,因为它并不关心具体的实现细节,而只关注于对象或类应该实现的方法或功能。

假设你正在编写一个程序,需要处理一些动物对象,比如狗、猫、鸟等等。你可以定义一个名为“动物”的接口,它规定了这些对象应该实现哪些方法,比如“吃饭”、“睡觉”、“叫声”等等。这些方法只是描述了动物应该具有的行为,而并不关心这些行为如何被实现。不同的动物可以根据自己的特点来实现这些方法,比如狗可能会发出“汪汪”声,而猫则可能会发出“喵喵”声。

总的来说,接口是一种必须遵守的约定与规范,这使得两个个体之间的交互得到了保证,并且其抽象的特性,又是规范具有灵活性而不用遵守死规矩。

怎么声明一个接口

接口类型的定义通常由一组方法签名组成,这些方法签名定义了接口的行为规范。如下:

type Greeter interface{
  Greet() string
}

interface{
  Add() int
}

这里我们声明了两个接口类型,其中一个名为Greeter,它里面有一个方法名为Greet,返回类型为string的方法签名;另外一个接口它没有类型名,因此它是匿名接口,其内部有一个方法名为Add,返回值类型为int的方法签名。

与结构体类似的,接口也允许嵌套,例子如下:

type Adder interface{
  Add(int, int) int
}

type Subtractor interface{
  Sub(int, int) int
}

type Calculator interface{
  Adder
  Subtractor
}

在这个例子中,我们声明了三个接口,分别是AdderSubtractor与嵌套了前面两者的CalCulatorAdder接口中有一个名为Add,参数类型为两个int类型,返回值类型为int类型的方法签名;Subtractor接口中有一个名为Sub,参数类型为两个int类型,返回值类型为int类型的方法签名。

怎么初始化一个接口变量

实现接口

接口没有被实现,那它是没有意义的,因此使用接口的第一步应该是先实现它:

type Cal struct{}

func (c Cal) Add(i1, i2 int) int {
	return i1 + i2
}

func (c Cal) Sub(i1, i2 int) int {
	return i1 - i2
}

Go的接口是隐式实现的,只要具体类型的方法集是接口方法的超集,就代表该类型实现了接口。

接口初始化

单纯的声明一个接口变量没有任何意义,接口只有被初始化为具体的类型是才有意义。其初始化条件同样符合「变量」赋值条件的基本逻辑,但是接口的初始化更加简单,因为只要右值的方法集是左值的超集就能赋值:

  1. 用结构体赋值

    var c Calculator
    c = Cal{}
    
  2. 用接口变量赋值

    var c2 Calculator = Cal{}
    var s Subtractor = c2
    

接口有什么用

举一个Gin框架路由的例子:

// IRouter defines all router handle interface includes single and group router.
type IRouter interface {
	IRoutes
	Group(string, ...HandlerFunc) *RouterGroup
}

// IRoutes defines all router handle interface.
type IRoutes interface {
	Use(...HandlerFunc) IRoutes

	Handle(string, string, ...HandlerFunc) IRoutes
	Any(string, ...HandlerFunc) IRoutes
	GET(string, ...HandlerFunc) IRoutes
	POST(string, ...HandlerFunc) IRoutes
	DELETE(string, ...HandlerFunc) IRoutes
	PATCH(string, ...HandlerFunc) IRoutes
	PUT(string, ...HandlerFunc) IRoutes
	OPTIONS(string, ...HandlerFunc) IRoutes
	HEAD(string, ...HandlerFunc) IRoutes
	Match([]string, string, ...HandlerFunc) IRoutes

	StaticFile(string, string) IRoutes
	StaticFileFS(string, string, http.FileSystem) IRoutes
	Static(string, string) IRoutes
	StaticFS(string, http.FileSystem) IRoutes
}

// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
	Handlers HandlersChain
	basePath string
	engine   *Engine
	root     bool
}

api := engine.Group("/api/v1")
api.GET("/tags", tag.List)

以上代码有两个接口,分别是IRouterIRoutes,还有一个结构体RouterGroupengine.Group方法将会创建一个RouterGroup,并将其赋值给apiapi的逻辑意义就是作为“/api/v1”则个组,后面再添加的路径,比如说“api/v1/test”或者“api/v1/t/a”之类的所有子路径都在api这个组别下。并且RouterGroup通过名字可以看出它的语义就是一个路由组,而它不但实现了IRouter,称为一个根路径,并且还实现了IRoutes,使其能够直接绑定子路由。

通过接口,不但让RouterGroup具有IRouterIRoutes两种状态,还制定了Router的处理标准与规范,这就是接口最大的意义——多态性、标准化