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

news/2025/3/16 2:44:30/

在这里插入图片描述

本文目录

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

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

文章来源:https://blog.csdn.net/theaipower/article/details/146182167
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ppmy.cn/news/1579463.html

相关文章

每日一题一一LeetCode1. 两数之和 - 力扣(LeetCode)

每日一题一一LeetCode1. 两数之和 - 力扣(LeetCode) 1. 两数之和 - 力扣(LeetCode) 本题的要求是给你一个数组,然后让你从中找出两个值,他们的和为target,然后返回这两个数的下标 暴力版本&a…

从0开始,手搓Tomcat

一、什么是Tomcat Tomcat 是一款开源的、轻量级的 Web 服务器,它不仅能够提供 HTTP 服务,还能够运行 Java Servlet 和 JavaServer Pages(JSP)。对于许多开发者来说,理解 Tomcat 的目录结构以及如何在该结构中组织应用…

如何使用postman就可以查看base64图片

一、应用场景 纯后端开发,想要知道自己返回的图片数据是否正常返回,使用简单的工具就可以解析 二、postman介绍 Postman 提供直观的图形用户界面,使用户能够轻松构建和发送 HTTP 请求。能够编写脚本,进行自动化测试,是…

Python网络爬虫之requests库的使用方法

requests库是Python中用于发送HTTP请求的一个重要库,在实际应用中,它被广泛用于爬取网页数据、调用API接口等。本节将详细讲解requests库的使用流程,包括发送HTTP请求、携带请求参数、处理服务器响应以及错误处理,帮助读者掌握requests库的基本使用方法。 1. 使用requests库…

Swift 手动导入 RxSwift.xcframework 报错

0x00 问题 The signature of “RxCocoa.xcframework” cannot be validated and may have been compromised. Validation Error: A sealed resource is missing or invalid 0x01 办法 手动修复签名,能 Build 成功! 打开终端,重新签名&…

MIFNet (论文阅读笔记)

Frequency-aware robust multidimensional information fusion framework for remote sensing image segmentation 用于遥感图像分割的频率感知鲁棒多维信息融合框架 Junyu Fan a, Jinjiang Li b, Yepeng Liu b, Fan Zhang b 论文地址 代码地址 1. 摘要 遥感图像复杂的三维结…

LVDS系列3:Xilinx的IOBUFDS原语

前面两节讲解了差分转单端的IBUFDS原语和单端转差分的OBUFDS原语,今天来讲一个同时带有两者功能的原语IOBUFDS; 前述的IBUFDS原语只能接收外部差分信号,此时连接管脚为input管脚,OBUFDS只能向外部输出差分信号,此时连接…

vue3 使用 el-popover实现换行

vue3 使用 el-popover实现换行 在<template #default>中填写内容 <!-- 经纬度地图拾取 --><el-popover placement"top-start" :title"$t(message.dataSearch.lonLatPick)" :width"400" trigger"hover"><templat…