思考一个使用闭包的场景
我们编写go程序时,对于复杂的程序都会用struct把代码进行分组分类。
不同的struct实现独立的任务,一个复杂的程序就有很多个struct配合
完成。struct之间的关系可以用接口interface解耦,这就是面向对象
编程的思路。
想象有这样的一个场景, 一个struct依赖一个接口,而且这个接口就一个
方法。接口可能有多个实现,每个实现就是一个struct,这个struct要
实现这个方法。
这个时候我们其实可以使用"函数类型"替代上面的接口定义,然后基于函数
类型写具体实现,这个用于替代上面的每个struct。用"函数类型"可以让
使用者清晰的知道这是个"动作",有不同的动作实现。
继续上面说到的用函数类型,如果我们用的函数类型也要依赖外部变量,比如
都有依赖一个结构体,那么这时候得用上闭包,也就是让函数类型的实现还得
依赖外部变量。
单个方法的接口
比如下面代码的例子,Shop商店可以给顾客发通知,所以它的Order方法
执行依赖一个接口NoticeI,这个接口提供的方法Send(msg string) (string, error)。
基于这个接口我们有多个实现,目前有两个,一个是发短信通知的叫MessageNotice,
一个是发邮件通知EmailNotice。每个实现都依赖一个UserInfo结构体,send方法会用到UserInfo的属性值。
使用时需要实例化UserInfo,然后基于它创建一个具体的NoticeI接口实现。
package main
import "fmt"
func main() {
shop := new(Shop)
user := &UserInfo{
Name: "Tom",
Phone: "12345",
Address: "北京市",
}
notice := &MessageNotice{
U: user,
}
shop.Order(notice, "you order is ok")
notice2 := &EmailNotice{
U: user,
}
shop.Order(notice2, "you order is ok")
}
type Shop struct {}
func (* Shop) Order(notice NoticeI, msg string) {
notice.Send(msg)
}
type UserInfo struct {
Name string
Phone string
Address string
Email string
}
type NoticeI interface {
Send(msg string) (string, error)
}
type MessageNotice struct {
U *UserInfo
}
func (c * MessageNotice) Send(msg string) (string, error) {
s := fmt.Sprintf("you message send out,%s", msg)
fmt.Println(s)
fmt.Println("===== to: ", c.U.Name)
return s, nil
}
type EmailNotice struct {
U *UserInfo
}
func (c *EmailNotice) Send(msg string) (string, error) {
s := fmt.Sprintf("you email send out %s", msg)
fmt.Println(s)
fmt.Println("===== to: ", c.U.Name)
return s, nil
}
用闭包替代接口实现
既然上面NoticeI接口只有一个方法定义,我们可以用函数类型,比如下面的func(string) (string, error)。
对于函数类型的实现,也是参照上面实现两个,一个支持短信发送,一个支持邮件发送。因为还得依赖于UserInfo,所以我们的
函数类型实现得用到闭包,即把外部的UserInfo引入进去。 具体看UserMessageNotice和UserEmailNotice。
所以我们总结出如下使用方式:
- 对于只有一个方法的接口,用函数类型可以取代。
- 对于上面接口的实现,我们就用函数类型的实现去取代。
- 接口实现还依赖外部变量,比如一个结构体对象,我们就得使用闭包。
func main() {
customer := new(Customer)
user := &UserInfo{
Name: "Tom",
Phone: "12345",
Address: "北京市",
}
customer.Order(UserEmailNotice(user), "you order is ok")
customer.Order(UserMessageNotice(user), "you order is ok")
}
type Customer struct {}
func (* Customer) Order(notice NoticeF, msg string) {
notice(msg)
}
type UserInfo struct {
Name string
Phone string
Address string
Email string
}
type NoticeF func(string) (string, error)
func UserMessageNotice(u *UserInfo) NoticeF {
return func(msg string) (string, error) {
s := fmt.Sprintf("you message send out,%s", msg)
fmt.Println(s)
fmt.Println("=====", u.Name)
return s, nil
}
}
func UserEmailNotice(u *UserInfo) NoticeF {
return func(msg string) (string, error) {
s := fmt.Sprintf("you email send out,%s", msg)
fmt.Println(s)
fmt.Println("=====", u.Name)
return s, nil
}
}
go-kit中关于闭包的使用
go-kit用来搭建微服务很合适,它把所有操作都定义为一种函数类型 endpoint。
transport对接协议,可能是http、grpc等,它根据协议不同解析出数据,然后交给endpoint处理。
endpoint是不关心协议的,不论哪种协议数据过来都是同一个endpoint处理。
endpoint的定义如下:
// Endpoint is the fundamental building block of servers and clients.
// It represents a single RPC method.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
但是对于真正的业务层面的处理,我们一般叫service,它会配合关系型数据库中间件、缓存redis、消息队列mq等等来完成复杂的业务逻辑。
那么,一个endpoint函数类型实现还得把工作交给service处理,这时候就得通过闭包实现。
// MakeEndPointArticleAdd 创建关于业务的构造函数
// 传入service层定义的相关业务接口
// 返回 endpoint.Endpoint, 实际就是一个函数签名
func MakeEndPointArticleAdd(svc my_service.IService) endpoint.Endpoint {
// 这里使用闭包,可以在这里做一些业务的处理
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
// request是对应请求过来时传入的参数,实际上是Transport中一个decode函数处理得到的
// 需要进行下断言
req := request.(my_service.ArticleAddRequest)
// 这里就是调用service层定义的业务逻辑
// 把拿到的数据作为参数
res := svc.ArticleAdd(ctx, req)
// 返回值可以是任意的,不过根据规范要返回我们刚才定义好的返回对象
return res, nil
}
}