Golang 支持将函数定义为类型,类型又能实现接口,这样就可以让函数类型实现接口,这样有什么好处呢?我们可以从一个实际的场景来体会一下这种用法。
场景
假设我们需要一个基于 Web 实现的打招呼功能。
当有请求访问时,如果请求要求皮卡丘出来接待,它会回复:皮卡皮卡;
如果请求要求妙蛙种子出来接待,它会回复:种子种子;
如果请求没有任何要求,那么默认回复:Hello World!
实现
- 先起一个 Web 服务
1 2 3 4 5 6
| func main() { http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World!") })) log.Fatal(http.ListenAndServe("localhost:8080", nil)) }
|
- 定义一个说话的接口
1 2 3 4
| type Sayer interface { Say() string }
|
- 定义皮卡丘的结构体,并实现说话接口
1 2 3 4 5 6 7
| type Pikachu struct{}
func (p Pikachu) Say() string { return "皮卡 皮卡!" }
|
- 定义妙蛙种子的结构体,并实现说话接口
1 2 3 4 5 6 7
| type Bulbasaur struct{}
func (b Bulbasaur) Say() string { return "种子 种子!" }
|
- 实现一个函数,用来默认响应请求
1 2 3 4 5 6 7 8 9 10 11 12
| type SayFunc func() string
func (s SayFunc) Say() string { return s() }
func DefaultSay() string { return "Hello, World!" }
|
- 定义一个 map,基于表驱动的方式响应请求
1 2 3 4 5
| m := map[string]Sayer{ "pikachu": Pikachu{}, "bulbasaur": Bulbasaur{}, "default": SayFunc(DefaultSay), }
|
- 实现处理请求的 handler
1 2 3 4 5 6 7 8 9 10 11 12 13
| http.Handle("/greet", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() who, ok := query["who"] if !ok { fmt.Fprintf(w, "say: %s", m["default"].Say()) return } if _, ok := m[who[0]]; !ok { fmt.Fprintf(w, "say: %s", m["default"].Say()) return } fmt.Fprintf(w, "%s say: %s", who[0], m[who[0]].Say()) }))
|
- 完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| package main
import ( "fmt" "log" "net/http" )
func main() { http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World!") }))
m := map[string]Sayer{ "pikachu": Pikachu{}, "bulbasaur": Bulbasaur{}, "default": SayFunc(DefaultSay), } http.Handle("/greet", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() who, ok := query["who"] if !ok { fmt.Fprintf(w, "say: %s", m["default"].Say()) return } if _, ok := m[who[0]]; !ok { fmt.Fprintf(w, "say: %s", m["default"].Say()) return } fmt.Fprintf(w, "%s say: %s", who[0], m[who[0]].Say()) }))
log.Fatal(http.ListenAndServe("localhost:8080", nil)) }
type Sayer interface { Say() string }
type SayFunc func() string
func (s SayFunc) Say() string { return s() }
func DefaultSay() string { return "Hello, World!" }
type Pikachu struct{}
func (p Pikachu) Say() string { return "皮卡 皮卡!" }
type Bulbasaur struct{}
func (b Bulbasaur) Say() string { return "种子 种子!" }
|
优点
从上述场景分析,我们定义了 Sayer 的接口,对于不同的招待对象,我们都得先定义结构体,然后用这个结构体实现 Sayer 接口。
例如皮卡丘和妙蛙种子,先定义 struct 再实现 Say 方法。
然而,有时候我们并不需要具体的对象,或者我们不在乎对象是谁,只需要有函数能满足处理即可,但是接口必须有类型才能实现,所以我们的代码可能写成这样
1 2 3 4 5 6 7 8 9 10 11 12 13
| type Default1 struct{} func (d Default1) Say() string { return "Hello, World!" }
type Default2 struct{} func (d Default2) Say() string { return "Hello, World!" } m := map[string]Sayer{ "default1": Default1{}, "default2": Default2{}, }
|
如果我们把函数先定义为类型,然后函数实现接口,那么后续的函数实现就不需要再定义结构体了,只需要在使用的时候做下类型转换,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| type SayFunc func() string
func (s SayFunc) Say() string { return s() }
func Default1Say() string { return "Hello, World!" }
func Default2Say() string { return "Hello, World!" }
m := map[string]Sayer{ "default1": SayFunc(DefaultSay1), "default2": SayFunc(DefaultSay2), }
|
经典实现
Golang 标准库提供 net/http 包里关于 Handler 的实现是比较经典的实现
http.Handle 函数需要传入两个参数,第一个参数 pattern 是请求路由,第二个参数是一个 interface,需要我们提供一个实现 Handler 接口的值。
假设我们需要实现一个计数器,我们可以这样实现
- 先定义计数器类型
- 实现 Handler 接口
- 作为参数传给 http.Handle
但是,大多数情况下,我们只想要为每个请求路由提供一个处理函数,而不是先定义一个类型,所幸的是 net/http 里面定义一个函数类型 HandlerFunc,它实现了 ServeHTTP 接口,并在实现里面回调自己,所以我们可以直接实现处理函数,然后作为参数传递给 http.Handle
的时候,用 HandlerFunc 类型转换一下即可