笔者在学习Go语言查询Elasticsearch的时候, 遇到了下面这句代码:

res, err = es.Search(

    es.Search.WithContext(context.Background()),

    es.Search.WithIndex("test"),

    es.Search.WithBody(strings.NewReader(query)),

    es.Search.WithTrackTotalHits(true),

    es.Search.WithPretty(),

)

很好奇es.Search究竟是一个什么东西, 但是Go语言的文档里面只写了:

type Search

type Search func(o ...func(*SearchRequest)) (*Response, error)

Search returns results matching a query. 

类型上面定义WithXXX方法没问题,但是Search本身是一个类型,类型怎么可以当作函数来调用呢?这究竟是怎样一种语法结构呢?

经过分析elasticsearch库的源代码,笔者发现原来这其中的奥秘就隐藏在newSearchFunc函数里面。

简单来说, es.Search是一个变量, 变量的值为一个函数, 所以我们可以调用es.Search()。函数体是定义在newSearchFunc函数里面的, 通过返回值返回后,赋给了es.Search变量。

而es.Search这个变量所属的类型上定义了很多With开头的方法, 这些方法可通过es.Search变量来访问, 所以我们可以调用es.Search.WithXXX()。

es是main函数里面的变量名, 不是包名。

es变量的类型是elasticsearch.Client, 也就是elasticsearch包里面定义的Client类型。

elasticsearch.Client类型是一个结构体, 首先他继承了esapi.API结构体(esapi包里面定义的API结构体), 然后增加了类型为estransport.Interface的Transport变量。

esapi.API结构体里面就有一个Search变量,变量的类型为esapi.Search,是一个函数类型,函数的参数列表是可变的。

因此, es.Search实际上是elasticsearch.Client结构体继承的esapi.API结构体里面的一个esapi.Search类型的变量。

在esapi.Search这个类型上又定义了很多With开头的方法,这些方法需要通过esapi.Search变量来访问,不能直接通过类型名访问。

也就是说我们可以执行es.Search.WithIndex("index")但是不能执行esapi.Search.WithIndex("index")。

因为es.Search是一个变量, 而esapi.Search是一个类型。

es.Search变量的类型是esapi.Search, WithXXX方法是在esapi.Search这个类型上定义的。

结构简化代码:

package main

import (
	"fmt"
	"strconv"
)

type Search func(o ...int) string

type API struct {
	Id int
	Value string
	Search Search
}

type Client struct {
	*API
	Transport string
}

func NewDefaultClient() *Client {
	return &Client{Transport: "Transport", API: New()}
}

func New() *API {
	return &API{Id: 10, Value: "hello", Search: newSearchFunc()}
}

func newSearchFunc() Search {
	return func(o ...int) string {
		s := ""
		for i, n := range o {
			if i != 0 {
				s += ","
			}
			s += strconv.Itoa(n)
		}
		return s
	}
}

func (s *Search) WithA() int {
	return -10
}

func (s *Search) WithB() int {
	return -20
}

func (s *Search) WithC(offset string) int {
	n, _ := strconv.Atoi(offset)
	return -30 + n
}

func main() {
	es := NewDefaultClient()
	fmt.Println(es.Id, es.Value, es.Search, es.Transport)
	str := es.Search(
		es.Search.WithA(), 
		es.Search.WithB(), 
		es.Search.WithC("-3"),
	)
	fmt.Println(str)
}

程序运行结果:

10 hello 0x49c300 Transport
-10,-20,-33

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐