一、方法与函数
面向对象的叫方法,面向过程的叫函数。
Java作为面向对象的语言,只有方法这个一说。而golang语言却有函数、方法两种说法。在golang语言中方法就是包含了接收者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:
func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}
二、go-redis 源码
最近看 go-redis 源码的时候发现代码中有些方法的接收者为函数类型,当时看的时候还是有点懵的,记录如下:
redis版本:v7.4.0
使用redis的业务代码如下:
type RDB struct {
goRedis *redis.Client
}
// 初始化Redis
func (this *RDB) Init() {
this.goRedis = redis.NewClient(&redis.Options{
Addr: "地址",
Password: "redis密码",
ReadTimeout: "读超时时间",
WriteTimeout: "写超时时间",
})
_, err := this.goRedis.Ping().Result()
}
//redis使用示例
func (this *RDB) SetValue(key string, value string, expireTime time.Duration) {
this.goRedis.SetNX(key, value, expireTime)
}
以下是源码部分:
redis.go 文件
// Client is a Redis client representing a pool of zero or more
// underlying connections. It's safe for concurrent use by multiple
// goroutines.
type Client struct {
*baseClient
cmdable
hooks
ctx context.Context
}
上面这部分代码定义了一个结构体 Client,包含有四个属性,其中 cmdable 在源码中是这样定义的:
commands.go 文件
type cmdable func(cmd Cmder) error
cmdable 是一个函数类型,他在之后将会频繁出现。
初始化 Client 的时候业务代码调用的是 redis.NewClient 方法,以下是 redis.Client 的源码部分:
// NewClient returns a client to the Redis Server specified by Options.
func NewClient(opt *Options) *Client {
opt.init()
c := Client{
baseClient: newBaseClient(opt, newConnPool(opt)),
ctx: context.Background(),
}
c.cmdable = c.Process
return &c
}
func (c *Client) Process(cmd Cmder) error {
return c.ProcessContext(c.ctx, cmd)
}
注意 c.cmdable = c.Process 这行代码,Process 是一个方法,入参为 Cmder,也就睡 redis 操作的具体执行命令,这里将其赋值给了 Client 的 cmdable。
下面的代码是设置 redis K-V的操作,并设置了超时时间。
commands.go文件
// Redis `SET key value [expiration] NX` command.
//
// Zero expiration means the key has no expiration time.
//SetNX 方法的接受者 cmdable 为一个函数类型
func (c cmdable) SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd {
var cmd *BoolCmd
if expiration == 0 {
// Use old `SETNX` to support old Redis versions.
cmd = NewBoolCmd("setnx", key, value)
} else {
if usePrecise(expiration) {
cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "nx")
} else {
cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "nx")
}
}
//上面的逻辑只是在构造参数cmd,真正的执行逻辑还是在 cmdable 这个函数类型中
_ = c(cmd)
return cmd
}
注意上面的代码块 _ = c(cmd) 之前的代码其实都是在构造 BoolCmd , 真正去执行 SetNX 操作的是 _ = c(cmd) 这行代码。这部分代码也是当时看源码时候的一个疑问点,知道具体的逻辑肯定是在 c(cmd) 里面,但不知道具体在哪实现的。
而 c 是这个方法接受者,是一个函数类型,并没有实际的业务逻辑,真正执行写入逻辑的其实是 Process 函数,在 NewClient 的时候 Client 的 cmdable 就已经是 Process了,这个时候就需要调用函数本体 Process
三、简单例子
上面贴出来的redis源码可能会有点绕,具体的可以自行看下源码,结合IDE可能会更直观些,上面那些代码简化的话也就是下面的样子。方法的接收者是一个函数结构体这种情况之前还没有见到过,初次见的时候还真的有点搞不懂他的执行顺序、逻辑是什么,后面查看了些资料后算下勉强搞懂了,然后记录下来。
package main
import (
"fmt"
)
func main() {
NewClient().Say()
}
//定义一个结构体
type Client struct {
cmdable
}
//定义一个函数类型,类似于上面redis例子的 cmdable 函数
type cmdable func(str string) error
//初始化操作,类似于上面redis例子的 NewClient 方法
func NewClient() *Client{
c := Client{
}
c.cmdable = SayChinese
return &c
}
//具体逻辑的函数,类似与上面redis例子中的 Process 方法
func SayChinese(str string) error{
/*
do something
*/
fmt.Println("输出内容 "+ str)
return nil
}
//接收者是一个函数类型,实际的执行逻辑是,类似与上面redis例子中的 SetNX 方法
func (c cmdable)Say() {
str := "测试"
//调用c()函数本体
c(str)
}