golang学习笔记——http.Handle和http.HandleFunc的区别与type func巧妙运用

文章目录

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类型。

参考资料

理解go的function types
golang中 type func() 用法分析

相关推荐
~kiss~6 小时前
A Survey of On-Policy Distillation for LLMs 学习(一)
学习
一只大鸽子7 小时前
游戏开发笔记4-像素画基础 轮廓-直线和曲线
笔记
小杨小杨、努力变强!7 小时前
python学习笔记
笔记·学习
杨逢昌工厂6S管理10 小时前
150-杨逢昌一线现场诊断笔记:越管一线越抵触背后的熵增机理与基准化治理
经验分享·笔记·职场和发展·学习方法
Star(up up up)13 小时前
Redis Windows 安装教程:使用Windows 的 Redis 压缩包
redis·笔记·学习·程序人生·云原生·学习方法·改行学it
希和202713 小时前
江科大 51 单片机课程笔记:DS1302 实时时钟 + LCD1602 可调电子钟(小白逐行讲解)
笔记·单片机·嵌入式硬件
岑梓铭14 小时前
《考研408数据结构》第七章(B树 和 B+树)复习笔记
数据结构·笔记·b树·考研·408
乌暮15 小时前
Spring AOP 与 Spring 事务:用大白话彻底理解原理与实践
java·数据库·spring boot·后端·sql·学习·spring
Go Work19 小时前
docker 占满根分区时先分清谁在吃盘
经验分享·笔记
30955666620 小时前
股票接口返回 404 与空数组的排查笔记:证书状态与级别参数
java·笔记·python·ios·iphone·股票数据api·股票数据接口