目录

Go沉思录朝花夕拾探究-Go-接口型函数

【Go沉思录】朝花夕拾:探究 Go 接口型函数

https://i-blog.csdnimg.cn/direct/d25f488a0907407ebb798de75458a970.png

之前写Geecache的时候,遇到了接口型函数,当时没有搞懂,现在重新回过头研究复习Geecache的时候,发现看得懂一些了,刚好能梳理下。

什么是接口型函数?比如下面这个 。

https://i-blog.csdnimg.cn/direct/0b8ab8917a4b4e6eba0dad0c89f0fe85.png

1.接口型函数

type Getter interface {
	Get(key string) ([]byte, error)
}

// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)

// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {
	return f(key)
}

还是上面图中的代码,我们来看看。

首先定义了一个接口 Getter ,只包含一个方法 Get(key string) ([]byte, error) ,紧接着定义了一个 函数类型 GetterFuncGetterFunc 参数和返回值与 GetterGet 方法是一致的。

而且 GetterFunc 还定义了 Get 方式,并在 Get 方法中 调用自己 ,这样就实现了接口 Getter 。所以 GetterFunc 是一个实现了接口的函数类型,简称为 接口型函数

接口型函数 只能应用于接口内部只定义了一个方法的情况 ,例如接口 Getter 内部有且只有一个方法 Get 。既然只有一个方法,为什么还要多此一举,封装为一个接口呢?

定义参数的时候,直接用 GetterFunc 这个函数类型不就好了,让用户直接传入一个函数作为参数,不更简单吗?

看案例之前,我们再梳理一下原理。

  • 首先定义了一个接口 Getter ,它要求实现一个 Get 方法
  • 然后定义了一个函数类型 GetterFunc ,其签名与 Get 方法相同
  • 最关键的是,为 GetterFunc 类型实现了 Get 方法,该方法内部直接调用函数本身。

这样,任何符合 GetterFunc 签名的函数都可以被转换为 Getter 接口类型,从而进行使用。


案例

假设 GetFromSource 的作用是从某数据源获取结果,接口类型 Getter 是其中一个参数,代表某数据源:

func GetFromSource(getter Getter, key string) []byte {
	buf, err := getter.Get(key)
	if err == nil {
		return buf
	}
	return nil
}

方式1 GetterFunc 类型的函数作为参数

我们可以用多种方式来实现这个这个函数。

比如方式一: GetterFunc 类型的函数作为参数。下面就是用一个匿名函数(GetterFunc类型)来作为参数。使用 GetterFunc() 将这个匿名函数转换为 GetterFunc 类型,这样它就实现了 Getter 接口

GetFromSource(GetterFunc(func(key string) ([]byte, error) {
	return []byte(key), nil
}), "hello")

也可以用普通的函数。

func test(key string) ([]byte, error) {
	return []byte(key), nil
}

func main() {
    GetFromSource(GetterFunc(test), "hello")
}

test 函数强制转换为 GetterFunc ,而 GetterFunc 实现了接口 Getter ,是一个合法的参数。

本质上,上面两种方式是类型转换,在go中我们定义了一个新类型,可以用这个新的类型名作为函数来进行类型转换,比如下面 字符串类型转换。

type String string

// 将普通字符串转换为 String 类型
str := String("1234")

我们把“函数”也看做是一种类型,(字符串、整数这些都是类型),那么也可以实现 函数 类型转换,比如。

type GetterFunc func(key string) ([]byte, error)

// 将匿名函数转换为 GetterFunc 类型
getter := GetterFunc(func(key string) ([]byte, error) {
    return []byte(key), nil
})

这两种情况本质上是相同的:都是将一个值转换为自定义类型。区别在于一个转换的是函数,另一个转换的是字符串。

// 使用普通函数作为数据源
func dbGetter(key string) ([]byte, error) {
    // 从数据库获取数据
    return []byte("value from db"), nil
}

// 将函数转换为Getter接口
var getter Getter = GetterFunc(dbGetter)

// 现在可以在任何需要Getter接口的地方使用
data, err := getter.Get("some_key")

方式2 实现了 Getter 接口的结构体作为参数

type DB struct{ url string}

func (db *DB) Query(sql string, args ...string) string {
	// ...
	return "hello"
}

func (db *DB) Get(key string) ([]byte, error) {
	// ...
	v := db.Query("SELECT NAME FROM TABLE WHEN NAME= ?", key)
	return []byte(v), nil
}

func main() {
	GetFromSource(new(DB), "hello")
}

DB 实现了接口 Getter ,也是一个合法参数。这种方式适用于逻辑较为复杂的场景,如果对数据库的操作需要很多信息,地址、用户名、密码,还有很多中间状态需要保持,比如超时、重连、加锁等等。这种情况下,更适合封装为一个结构体作为参数。


价值

综上,这样,既能够将普通的函数类型(需类型转换)作为参数,也可以将结构体作为参数,使用更为灵活,可读性在使用函数(方式1)的时候某种程度上会更好,这就是接口型函数的价值。

2.net/http包中的使用场景

上面的特性,在标准库中用得很多, net/httpHandlerHandlerFunc 就是一个典型。

看看 Handler 定义。

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

我们可以 http.Handle 来映射请求路径和处理函数,Handle 的定义如下所示。

func Handle(pattern string, handler Handler)

这里需要的第二参数是接口类型 Handler

func home(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte("hello, index page"))
}

func main() {
	http.Handle("/home", HandlerFunc(home))
	_ = http.ListenAndServe("localhost:8000", nil)
}

通常还有另一个函数, http.HandleFuncHandleFunc 的定义如下:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

第二个参数是一个普通的函数类型,那可以直接将 home 传递给 HandleFunc ,实现代码如下。

func main() {
	http.HandleFunc("/home", home)
	_ = http.ListenAndServe("localhost:8000", nil)
}

看看 HandleFunc 的内部实现逻辑。

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

可以看到, mux.Handle(pattern, HandlerFunc(handler))

两种写法是完全等价的,内部将第二种写法转换为了第一种写法。