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

day2加餐 Go 接口型函数的使用场景

文章目录

  • 问题
  • 价值
  • 使用场景
  • 其他语言类似特性

问题

在 动手写分布式缓存 - GeeCache day2 单机并发缓存 这篇文章中,有一个接口型函数的实现:

// A Getter loads data for a key.
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 这个函数类型不就好了,让用户直接传入一个函数作为参数,不更简单吗?

所以呢,接口型函数的价值什么?

价值

我们想象这么一个使用场景,GetFromSource 的作用是从某数据源获取结果,接口类型 Getter 是其中一个参数,代表某数据源:

func GetFromSource(getter Getter, key string) []byte {buf, err := getter.Get(key)if err == nil {return buf}return nil
}

我们可以有多种方式调用该函数:

方式一:GetterFunc 类型的函数作为参数

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 强制类型转换为 GetterFuncGetterFunc 实现了接口 Getter,是一个合法参数。这种方式适用于逻辑较为简单的场景。

方式二:实现了 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,也是一个合法参数。这种方式适用于逻辑较为复杂的场景,如果对数据库的操作需要很多信息,地址、用户名、密码,还有很多中间状态需要保持,比如超时、重连、加锁等等。这种情况下,更适合封装为一个结构体作为参数。

这样,既能够将普通的函数类型(需类型转换)作为参数,也可以将结构体作为参数,使用更为灵活,可读性也更好,这就是接口型函数的价值。

使用场景

这个特性在 groupcache 等大量的 Go 语言开源项目中被广泛使用,标准库中用得也不少,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)
}

摘自 Go 语言源代码 net/http/server.go

我们可以 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", http.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))
}

如果你仔细观察,会发现 http.ListenAndServe 的第二个参数也是接口类型 Handler,我们使用了标准库 net/http 内置的路由,因此呢,传入的值是 nil。那如果这个地方我们传入的是一个实现了 Handler 接口的结构体呢?就可以完全托管所有的 HTTP 请求,后续怎么路由,怎么处理,请求前后增加什么功能,都可以自定义了。慢慢地,就变成了一个功能丰富的 Web 框架了。如果你感兴趣呢,可以阅读 7天用Go从零实现Web框架Gee教程。

其他语言类似特性

如果有 Java 编程经验的同学可能比较有感触。Java 1.5 中是不支持直接传入函数的,参数要么是接口,要么是对象。举一个最简单的例子,列表自定义排序时,需要实现一个匿名的 Comparator 类,重写 compare 方法。

Collections.sort(list, new Comparator<Integer>(){@Overridepublic int compare(Integer o1, Integer o2) {return o2 - o1;}
});

Java 1.8 中引入了大量的函数式编程的特性,其中 lambda 表达式和函数式接口就是一个很好的简化 Java 写法的特性。Java 1.8 中,上述的例子可以简化为:

Collections.sort(list, (Integer o1, Integer o2) -> o2 - o1 );

即从需要构造一个匿名对象简化为只需要一个lambda函数表达式,可以认为是面向对象与函数式编程的一种结合。同样地,这种写法只支持只定义了一个方法的接口类型,因为只定义了一个方法的接口,就会很明确传入进来的函数对应接口中的哪个方法。正是这种结合,可以达到实现相同代码,代码量更少的目的。

http://www.lryc.cn/news/403951.html

相关文章:

  • 摄像头 RN6752v1 视频采集卡
  • 记录vivado自带IP iBert眼图近端回环
  • js | Core
  • Log4J reminder
  • Unity XR Interaction Toolkit(VR、AR交互工具包)记录安装到开发的流程,以及遇到的常见问题(一)!
  • MongoDB文档整理
  • 【AI学习】关于Scaling Law的相关学习
  • 学习小记-Kafka相较于其他MQ有啥优势?
  • 技能 | postman接口测试工具安装及使用
  • 移动UI:任务中心的作用,该如何设计更合理?
  • pytorch学习(十)优化函数
  • Ubuntu22.04:安装Samba
  • Powershell 使用介绍
  • 【Langchain大语言模型开发教程】记忆
  • 最新Qt6的下载与成功安装详细介绍
  • LeetCode 热题 HOT 100 (001/100)【宇宙最简单版】
  • Ubantu 使用 docker 配置 + 远程部署 + 远程开发
  • 应用层自定义协议与序列化
  • Python学习笔记—100页Opencv详细讲解教程
  • C语言·分支和循环语句(超详细系列·全面总结)
  • Gateway源码分析:路由Route、断言Predicate、Filter
  • ARM体系结构和接口技术(十)按键中断实验①
  • PostgreSQL使用(二)——插入、更新、删除数据
  • 有关css的题目
  • 【开源库】libodb库编译及使用
  • 电力需求预测挑战赛笔记 Task3 #Datawhale AI 夏令营
  • Promise 详解(原理篇)
  • 动态内存经典笔试题分析
  • JS设计模式(一)单例模式
  • uniapp动态计算并设置元素高度