一、有名函数和匿名函数
func (InputTypeList) OutputTypeList
//无参函数
func fun() {
}
var f func()//无入参无返回值的函数对象声明,初始值为nil
f = fun
//有参函数
type FT func(int)
func Fa(int){}
func Test(FT){}
Test(Fa) //pass function as parameter
type NewType OldTypetype NewFuncType FuncLiteral
type CalculateType func(int, int) // 声明了一个函数类型
// 该函数类型实现了一个方法
func (c *CalculateType) Serve() {
fmt.Println("我是一个函数类型")
}
// 加法函数
func add(a, b int) {
fmt.Println(a + b)
}
// 乘法函数
func mul(a, b int) {
fmt.Println(a * b)
}
func main() {
a := CalculateType(add) // 将add函数强制转换成CalculateType类型
b := CalculateType(mul) // 将mul函数强制转换成CalculateType类型
a(2, 3)
b(2, 3)
a.Serve()
b.Serve()
}
二、方法作为函数变量传递
当特定对象实例的方法method作为函数指针时传递时,接受者会保证在调用的时候,调用到是这个对象实例的method,method的任何操作都会针对该对象实例生效,而且不需要传任何类似于this、self指针之类的东西,换句话说,对象实例+method 作为绑定的整体传递给接受者的
type Outer struct{
a string
}
func (o *Outer)Hello(in *Inner){
fmt.Println("Inner:",in,"\tcall\tOuter:",o.a)
}
type Inner struct{
a string
cb Cb
}
type Cb func(*Inner)
func (in *Inner)Register(cb Cb){
in.cb = cb
}
func (in *Inner)Say(){
in.cb(in)
}
func main() {
out1 := Outer{a:"out1 instance"}
out2 := Outer{a:"out2 instance"}
fmt.Println("out1 :",&out1)
fmt.Println("out2 :",&out2)
in1 := Inner{a:"int1 instance"}
in1.Register(out1.Hello)
in1.Say()
in1.Register(out2.Hello)
in1.Say()
}
三、闭包构成的三种情况
闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)。
// 第一种场景
func fib01() func() int {
return func() int {
a, b := 0, 1
a, b = b, a+b
return a
}
}
var y int
// 第二种场景
func fib00() func() int {
return func() int {
y++
return y
}
}
func AntherExFunc(n int) func() {
n++
return func() {
fmt.Println(n)
}
}
func ExFunc(n int) func() {
return func() {
n++
fmt.Println(n)
}
}
func main() {
myAnotherFunc:=AntherExFunc(20)
fmt.Println(myAnotherFunc) //0x48e3d0 在这儿已经定义了n=20 ,然后执行++ 操作,所以是21 。
myAnotherFunc() //21 后面对闭包的调用,没有对n执行加一操作,所以一直是21
myAnotherFunc() //21
myFunc:=ExFunc(10)
fmt.Println(myFunc) //0x48e340 这儿定义了n 为10
myFunc() //11 后面对闭包的调用,每次都对n进行加1操作。
myFunc() //12
}
四、for循环中的并发闭包:
//因为for语句里面中闭包使用的v是外部的v变量,当执行完循环之后,v最终是c,所以如果在主协程执行完for之后,定义的子协程才开始执行结果可能是ccc,
//如果for过程中,子协程先执行了,结果就可能不是c, c,c”。
func test1() {
s := []string{"a", "b", "c"}
for _, v := range s {
go func() {
fmt.Println(v)
}()
}
time.Sleep(time.Second * 1)
}
//for程序如果想输出a,b,c的解决方法:
//只需要每次将变量v的拷贝传进函数即可,但此时就不是使用的上下文环境中的变量了。
func test2() {
s := []string{"a", "b", "c"}
for _, v := range s {
go func(v string) {
fmt.Println(v)
}(v) //每次将变量 v 的拷贝传进函数
}
select {}
}
//结果为4 4 4 4 4,函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4
func test1() {
var users [5]struct{}
for i := range users {
defer func() { fmt.Println(i) }()
}
}
//defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份利用函数参数拷贝,输出为 4 3 2 1 0
func test1() {
var users [5]struct{}
for i := range users {
defer Print(i)
}
}
func Print(i int) {
fmt.Println(i)
}
select语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select会随机地选取其中之一执行。select {}会一直阻塞。很多时候我们需要让main函数不退出,利用select {}让它在后台一直执行
func main() {
for i := 0; i < 20; i++ { //启动20个协程处理消息队列中的消息
c := consumer.New()
go c.Start()
}
select {} // 阻塞
}
五、闭包的应用场景和作用:
1、延迟调用,关键字 defer 用于注册延迟调用。defer 调用会在当前函数执行结束前才被执行,这些调用被称为延迟调用 。defer 中使用匿名函数依然是一个闭包。
func main() {
x, y := 1, 2
defer func(a int) {
fmt.Printf("x:%d,y:%d\n", a, y) // y 为闭包引用
}(x) // 复制 x 的值
x += 100
y += 100
fmt.Println(x, y)
}
2、变量空间隔离,避免变量污染。外部无法对闭包引用的上下文变量进行直接操作,保证了私有性。
// 函数计数器 利用闭包每个计数器有自己独立未暴露的sum
func counter(f func()) func() int {
sum := 0
return func() int {
f()
sum += 1
return sum
}
}
// 测试的调用函数
func foo() {
fmt.Println("call foo")
}
func main() {
cnt := counter(foo)
cnt()
cnt()
cnt()
fmt.Println(cnt())
}
/*
输出结果:
call foo
call foo
call foo
call foo
4
*/
---
//加法器
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
myAdder := adder()
// 从1加到10
for i := 1; i <= 10; i++ {
myAdder(i)
}
fmt.Println(myAdder(0))
// 再加上45
fmt.Println(myAdder(45))
}
---
//斐波那契闭包处理
func fibonacci() func() int {
b0 := 0
b1 := 1
return func() int {
tmp := b0 + b1
b0 = b1
b1 = tmp
return b1
}
}
func main() {
myFibonacci := fibonacci()
for i := 1; i <= 5; i++ {
fmt.Println(myFibonacci())
}
}
3、装饰函数:函数是Go语言的一等公民,可以作为函数的参数进行传递。装饰器指的就是函数作为参数进行传递的情况。用于构造函数中对对象的封装和处理(尤其是成员较多的结构体)。闭包函数中本身是没有定义变量,而是引用了它所在的环境中的变量。
// Options 调用参数 被装饰的结构体
type Options struct {
...
Discovery discovery.Discovery
LoadBalance loadbalance.LoadBalancer
CircuitBreaker circuitbreaker.CircuitBreaker
}
// Option 调用参数工具函数
type Option func(*Options)
// WithDiscovery 指定服务发现
func WithDiscovery(d discovery.Discovery) Option {
return func(o *Options) {
o.Discovery = d
}
}
...//其它类似的装饰函数省略。。。。
// selector默认实现,内部自动串好 服务发现 负载均衡 熔断隔离 等流程
type Selector struct{}
// Select 输入service name,返回一个可用的node
func (s *Selector) Select(serviceName string, opt ...Option) (*registry.Node, error) {
...
opts := &Options{
Discovery: discovery.DefaultDiscovery,
LoadBalance: loadbalance.DefaultLoadBalancer,
CircuitBreaker: circuitbreaker.DefaultCircuitBreaker,
}
for _, o := range opt {
o(opts)
}
....
}
//newSelector
func newSelector(registry discovery.DefaultDiscovery)*registry.Node {
selector := &Selector{}
n, err := selector.Select("configServer",WithDiscovery(registry ))
....
return n
}
4、闭包引用的上下文变量会延长生命期,避免再次传递。
//普通函数:传递根据哪个后缀判断,其次是文件名字
func makeSuffix (suffix string, name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix //如果没有后缀就拼接
}
return name
}
func main(){
fmt.Println("文件名处理后:", makeSuffix("jpg","go语言圣经"))
fmt.Println("文件名处理后:", makeSuffix("jpg","PHP设计模式.jpg"))
}
---
//闭包函数,生成函数可以传入一个文件名,如果该文件名没有指定的后缀(如.jpg),则返回.jpg,如果有则全称
func makeSuffix (suffix string) func (string) string {
return func (name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix //如果没有后缀就拼接
}
return name
}
}
func main(){
//先返回一个闭包
test := makeSuffix(".jpg")
fmt.Println("文件名处理后:", test("go语言圣经"))
fmt.Println("文件名处理后:", test("PHP设计模式.jpg"))
}
5、内嵌匿名函数的并发调用,原因代码逻辑有for循环或处理时间较长的逻辑需要匿名函数包住另起协程运行,从而不阻塞影响后续逻辑。或者有defer等调用次序的问题需要在匿名函数中先调用defer等。
func main() {
ch := make(chan struct{})
go func() {
fmt.Println("do something..")
for {
// process
}
time.Sleep(time.Second * 1)
ch <- struct{}{}
}()
<-ch
fmt.Println("I am finished")
}
6.拦截器的使用之中间件模式
多个中间件会形成一个栈结构(middle stack),以"先进后出"(first-in-last-out)的顺序执行,被称为洋葱结构。
如洋葱模型所示:调用请求先被第一个filter处理,然后依次交给后续的filter,最后交给业务逻辑处理。处理完之后又从内层的filter一层一层向外层返回,最后返回给客户端。请求先一层一层地进入洋葱,再一层一层地出来,这两个地方都可以写逻辑,也就是所谓的hook。先执行fitler函数,其中最后执行的是业务逻辑函数。
Handle的logic函数其实就是洋葱的中心——业务逻辑,这个在调用Handle时由框架设置好。然后Handle内部是一个递归调用的闭包,如果所有filter都执行完了,就执行logic函数,如果后面还有filter就执行filter.
// Chain 链式过滤器
type Chain []Filter
// Handle 链式过滤器递归处理流程
func (fc Chain) Handle(ctx context.Context, req interface{}, rsp interface{}, logic HandleFunc) (err error) {
n := len(fc)
curI := -1
var chainFunc HandleFunc
chainFunc = func(ctx context.Context, req interface{}, rsp interface{}) error {
if curI == n-1 {
return logic(ctx, req, rsp)
}
curI++
err := fc[curI](ctx, req, rsp, chainFunc)
return err
}
return chainFunc(ctx, req, rsp)
}