每日一课,不论长短,有所学有所

业精于勤技在专,行则将至事必成

在看go的开源项目的时候,经常会遇到接口定义,构造函数,对象定义等;代码结构的设计反倒比业务实现的逻辑更重要,作者所谓的逻辑无非跟你想的一样或者比你略好,但作者对代码结构的拆分和设计中是包含着作者的编程经验和独立思考的,因此初入门的时候,我们会因为懂代码却看不懂源码,往往是代码结构的拆分或者说是代码的高级写法所导致的。今天来搞懂一个golang中的函数类型。

cmdable
// 1
type Client struct {
*baseClient
cmdable
hooks
ctx context.Context
}

// 2
type cmdable func(cmd Cmder) error

// 3
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)
}

代码1定义了结构体Client,代码2是个函数类型cmdable,代码3是构造函数(所谓构造函数的意思就是创建对象时初始化对象,为对象的成员变量赋初始值)

c.cmdable = c.Process
ClientcmdableProcesscmdable

下面举个例子,应用一下函数类型:

package main

import (
"fmt"
)

func main() {
NewClient().Say2Tom()
NewClient().Say2Lily()
}

//定义一个结构体
type Client struct {
cmdable
}

//定义一个函数类型
type cmdable func(str string) error

//构造函数,初始化操作
func NewClient() *Client {
c := Client{}
c.cmdable = SayChinese
return &c
}

//处理一些具体逻辑的函数
func SayChinese(str string) error {
/*
do something
*/
fmt.Println("hello " + str)
return nil
}

//实现Client的一些方法,方法中调用了cmdable
func (c *Client) Say2Tom() {
c.cmdable("tom")
}

func (c *Client) Say2Lily() {
c.cmdable("lily")
}
NewClient().Say2Tom()Say2TomcmdableNewClientc.cmdable = SayChineseSayChinese

这样做的好处是,当我们业务有变的时候,我们可以通过修改cmdable的赋值,既可以完成更改SayChinese函数的逻辑,使代码的适应性更强。

为了让大家更好的认识到函数类型的使用,再举个通过函数实现接口的例子,来对比结构体实现接口的优势:

// 结构体实现接口
package main

import (
"fmt"
)

func main() {
// 声明接口变量
var sayWord SayWord
// 实例化结构体
s := NewSturct()
sayWord = s
sayWord.Call("hello")
}

// 接口
type SayWord interface {
Call(string)
}

// 结构体类型
type Person struct {
Name string
}

// 实例化结构体
func NewSturct() SayWord {
a := new(Person)
a.Name = "lily say "
return a
}

// 实现结构体的Call方法
func (s *Person) Call(word string) {
fmt.Println(s.Name + word)
}
// 函数实现方法
package main

import "fmt"

func main() {
// 声明接口变量
var sayWord SayWord
// 将函数转为FuncCaller类型,再赋值给接口
sayWord = FuncCaller(Tom)
// 使用接口调用FuncCaller.Call,内部会调用函数本体
sayWord.Call("hello")
}

func Tom(v string) {
fmt.Println("tom say", v)
}

// 接口
type SayWord interface {
Call(string)
}

// 函数定义为类型 函数的入参为string类型
type FuncCaller func(string)

// 实现函数的方法Call
func (f FuncCaller) Call(word string) {
// 调用f函数本体
f(word)
}

函数实现方法和结构体实现方法,二者都可以较好的适应扩展,前者可以通过自定义函数来作为函数类型的入参,自定义处理逻辑;FuncCaller 无须被实例化,只需要将函数转换为 FuncCaller 类型即可,函数来源可以是命名函数、匿名函数或闭包