本文目录
- 序
- 1.接口型函数
- 案例
- 方式1 GetterFunc 类型的函数作为参数
- 方式2 实现了 Getter 接口的结构体作为参数
- 价值
- 2.net/http包中的使用场景
序
之前写Geecache的时候,遇到了接口型函数,当时没有搞懂,现在重新回过头研究复习Geecache的时候,发现看得懂一些了,刚好能梳理下。
什么是接口型函数?比如下面这个 。
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)
,紧接着定义了一个函数类型 GetterFunc
,GetterFunc
参数和返回值与 Getter
中 Get
方法是一致的。
而且 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/http
的 Handler
和 HandlerFunc
就是一个典型。
看看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.HandleFunc
,HandleFunc
的定义如下:
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))
两种写法是完全等价的,内部将第二种写法转换为了第一种写法。