文章目录
- 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类型。