当前位置: 首页 > article >正文

【Go沉思录】朝花夕拾:探究 Go 接口型函数

在这里插入图片描述

本文目录

  • 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),紧接着定义了一个函数类型 GetterFuncGetterFunc 参数和返回值与 GetterGet 方法是一致的。

而且 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/httpHandlerHandlerFunc 就是一个典型。

看看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.HandleFuncHandleFunc 的定义如下:

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))

两种写法是完全等价的,内部将第二种写法转换为了第一种写法。


http://www.kler.cn/a/582269.html

相关文章:

  • 烟火烟雾明火分割数据集labelme格式4065张2类别
  • Pycharm中脚本执行的3种模式——unittest框架、pytest框架及普通模式
  • Android Compose remember 详解
  • mysql表的创建
  • Go 语言编程全解析:Web 微服务与数据库十大专题深度精讲
  • 【商城实战(23)】筑牢安全防线,防范常见漏洞
  • 免费送源码:Java+PHP+MySQL “爱学术”期刊采编系统的设计与实现 计算机毕业设计原创定制
  • AI+视频监控电力巡检:EasyCVR视频中台方案如何赋能电力行业智能化转型
  • Photoshop 中如何快速抠图?
  • NGINX介绍--鱼皮老师课程学习笔记
  • win32汇编环境,网络编程入门之一
  • Vue 3 中,将静态资源(如图片)转换为 URL
  • Orale数据文件加错位置,你直接rm引发的故障
  • 【RAG】RAG 系统的基本搭建流程(ES关键词检索示例)
  • maven的项目构建
  • Web3.0 从入门到实战:一站式开发指南
  • 在MATLAB中实现PID控制仿真
  • Vue3——Fragment
  • MVCC实现原理
  • Express + MongoDB 实现登录验证码