golang闭包

思考一个使用闭包的场景

我们编写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
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值