文章目录
- http.Handle和http.HandleFunc的区别
- http.Handle分析
-
- [type func巧妙运用](#type func巧妙运用)
- http.HandleFunc分析
- 总结
- 参考资料
http.Handle和http.HandleFunc的区别
http.Handle和http.HandleFunc的区别体现了Go语言接口的巧妙运用
下面代码启动了一个 http 服务器,监听 8080 端口,并注册路由。实现这两个路由注册的方法有点不同,一个使用 http.Handle
,另一个使用 http.HandleFunc
,下面来看看这两个之间的区别;
http.Handle分析
我们简单看一下http.Handle
函数
这个 Handler
类型是什么呢,其实它就是一个接口
,包含一个 ServeHttp()
的方法:
go
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
在Go语言常规代码中,接口一般是这样用的
go
// Sayer 接口
type Sayer interface {
say()
}
type dog struct {}
type cat struct {}
// dog实现了Sayer接口
func (d dog) say() {
fmt.Println("汪汪汪")
}
// cat实现了Sayer接口
func (c cat) say() {
fmt.Println("喵喵喵")
}
先写一个接口,再写一个结构体,最后将结构体与方法相关联,也就是这个结构体类型dog实现了接口。
其实
type
关键字作用是声明类型
,这里应该写为dog类型
实现Sayer接口,cat类型
实现Sayer接口,比较合适。(一个对象只要全部实现了接口中的方法,那么就实现了这个接口。)
我们再看一个例子
go
package main
import "fmt"
type Sayer interface {
say()
}
type dog int
// dog实现了Sayer接口
func (d dog) say() {
fmt.Println("汪汪汪")
}
func main() {
var d dog
d.say()
}
没错,我们把int
类型重新定义为一个新的类型,这个类型叫dog
,dog
这个类型也实现了Sayer
接口。所以,玩转一个接口一般分为三步:
- 第一步定义一个接口类型,
- 第二步定义一个
非接口类型
, - 第三步在非接口类型上实现接口。
所以,第二步的代码只要定义一个非接口类型
即可。
type func巧妙运用
我们知道,func
也是一种类型,那可以试着用func
类型实现接口。
go
type Sayer interface {
say(str string)
}
type Dog func(string)
func (f Dog) say(str string) {
f(str)
}
代码写到IDE
上也没有报错,说明这段代码是可行的。代码中的f(str)
又是什么意思。f(str)
看起来有点像一个名叫f
的函数,传入了一个str
的参数,当方法被调用时(注意,Go语言中方法与函数的区别),这个函数就行执行,只不过这个函数的类型是Dog
类型。等等,有点不对。这个函数就行执行,执行了什么?所以,执行之前一定要有一个Dog类型的实例。可以是一个有名称的函数,也可以是一个匿名函数,
go
package main
import "fmt"
type Sayer interface {
say(str string)
}
type Dog func(string)
func (f Dog) say(str string) {
f(str)
}
func main() {
d := Dog(func(str string){
fmt.Println("转换类型")
})
d("开始执行")
}
特别要注意的是这段代码中的Dog(func(str string)
表示是一个强制转换,把匿名函数转为Dog类型。理解了type func
,再看方法二也就不难了。
http.HandleFunc分析
从代码上看函数二比函数一少定义了一个结构体,简洁一些,写起来也方便一些。那为什么函数二比函数一少写了一个结构体? 下面看代码
go
func HandleFunc(pattern string,
handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
通过源码得知HandleFunc
是http包下的一个大写字母开头的公开函数,该函数接收两个参数,一个是路由匹配的字符串,另外一个是 func(ResponseWriter, *Request)
类型的函数。
然后继续调用 DefaultServeMux.HandleFunc(pattern, handler)
:
go
func (mux *ServeMux) HandleFunc(pattern string,
handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
可以看到,代码中的第6行,HandlerFunc(handler)
,HandlerFunc可不是一个函数,而是类型转换
。
Go语言中只有强制类型转换,没有隐式类型转换。下面的语法只能在两个类型之间支持相互转换的时候使用。
-
强制类型转换的基本语法如下:
T(表达式)
其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等。
这里是把 handler
转换成了 HandlerFunc
类型,而 HandlerFunc
类型则如下所示:
go
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
该类型实现了 Handler
接口(因为该类型有一个ServeHTTP方法),所以其也可以转换成 Handler
类型,接下来调用 mux.Handle(pattern string, handler Handler)
就跟 http.Handle
的流程是一样的了。
bash
Handler接口 ---> HandlerFunc类型
︿ \
/ \
/ ﹀
匿名函数 ---> 强制转换 HandlerFunc类型的ServeHTTP方法调用HandlerFunc类型的函数
总结
http.Handle
的使用方法和面向对象编程差不多,第一步定义一个接口类型,第二步定义一个非接口类型
,第三步在非接口类型上实现接口。
http.HandleFunc
使用上简洁一些,但原理比较复杂。先定义一个函数(参数类型,返回值都要和ServeHTTP一样),再将它强转为HandlerFunc
类型,HandlerFunc
类型有一个方法叫ServeHTTP
,这方法会执行一个名叫f
的函数,这个函数的类型就是HandlerFunc
。因为一个对象只要全部实现了接口中的方法,那么就实现了这个接口。所以,HandlerFunc
类型实现了type Handler interface
这个接口。所以,我们只要写一个匿名函数,内部就会为我们转为HandlerFunc
类型。