109 | Go 编程模式:Functional Options
下载APP
关闭
渠道合作
推荐作者
109 | Go 编程模式:Functional Options
2021-01-14 陈皓 来自北京
《左耳听风》
课程介绍
讲述:杨超
时长05:40大小5.18M
你好,我是陈皓,网名左耳朵耗子。
这节课,我们来讨论一下 Functional Options 这个编程模式。这是一个函数式编程的应用案例,编程技巧也很好,是目前 Go 语言中最流行的一种编程模式。
但是,在正式讨论这个模式之前,我们先来看看要解决什么样的问题。
配置选项问题
在编程中,我们经常需要对一个对象(或是业务实体)进行相关的配置。比如下面这个业务实体(注意,这只是一个示例):
在这个 Server 对象中,我们可以看到:
要有侦听的 IP 地址 Addr 和端口号 Port ,这两个配置选项是必填的(当然,IP 地址和端口号都可以有默认值,不过这里我们用于举例,所以是没有默认值,而且不能为空,需要是必填的)。
然后,还有协议 Protocol 、 Timeout 和MaxConns 字段,这几个字段是不能为空的,但是有默认值的,比如,协议是 TCP,超时30秒 和 最大链接数1024个。
还有一个 TLS ,这个是安全链接,需要配置相关的证书和私钥。这个是可以为空的。
所以,针对这样的配置,我们需要有多种不同的创建不同配置 Server 的函数签名,如下所示:
因为 Go 语言不支持重载函数,所以,你得用不同的函数名来应对不同的配置选项。
配置对象方案
要解决这个问题,最常见的方式是使用一个配置对象,如下所示:
我们把那些非必输的选项都移到一个结构体里,这样一来, Server 对象就会变成:
于是,我们就只需要一个 NewServer() 的函数了,在使用前需要构造 Config 对象。
这段代码算是不错了,大多数情况下,我们可能就止步于此了。但是,对于有洁癖的、有追求的程序员来说,他们会看到其中不太好的一点,那就是Config 并不是必需的,所以,你需要判断是否是 nil 或是 Empty—— Config{}会让我们的代码感觉不太干净。
Builder 模式
如果你是一个 Java 程序员,熟悉设计模式的一定会很自然地使用 Builder 模式。比如下面的代码:
这样一来,就可以使用这样的方式了:
这种方式也很清楚,不需要额外的 Config 类,使用链式的函数调用的方式来构造一个对象,只需要多加一个 Builder 类。你可能会觉得,这个 Builder 类似乎有点多余,我们似乎可以直接在Server 上进行这样的 Builder 构造,的确是这样的。但是,在处理错误的时候可能就有点麻烦,不如一个包装类更好一些。
如果我们想省掉这个包装的结构体,就要请出 Functional Options 上场了:函数式编程。
Functional Options
首先,我们定义一个函数类型:
然后,我们可以使用函数式的方式定义一组如下的函数:
这组代码传入一个参数,然后返回一个函数,返回的这个函数会设置自己的 Server 参数。例如,当我们调用其中的一个函数 MaxConns(30) 时,其返回值是一个 func(s* Server) { s.MaxConns = 30 } 的函数。
这个叫高阶函数。在数学上,这有点像是计算长方形面积的公式为: rect(width, height) = width * height; 这个函数需要两个参数,我们包装一下,就可以变成计算正方形面积的公式:square(width) = rect(width, width) 。也就是说,squre(width)返回了另外一个函数,这个函数就是rect(w,h) ,只不过它的两个参数是一样的,即:f(x) = g(x, x)。
好了,现在我们再定一个 NewServer()的函数,其中,有一个可变参数 options ,它可以传出多个上面的函数,然后使用一个 for-loop 来设置我们的 Server 对象。
于是,我们在创建 Server 对象的时候,就可以像下面这样:
怎么样,是不是高度整洁和优雅?这不但解决了“使用 Config 对象方式的需要有一个 config 参数,但在不需要的时候,是放 nil 还是放 Config{}”的选择困难问题,也不需要引用一个 Builder 的控制对象,直接使用函数式编程,在代码阅读上也很优雅。
所以,以后,你要玩类似的代码时,我强烈推荐你使用 Functional Options 这种方式,这种方式至少带来了 6 个好处:
直觉式的编程;
高度的可配置化;
很容易维护和扩展;
自文档;
新来的人很容易上手;
没有什么令人困惑的事(是 nil 还是空)。
参考文档
Self referential functions and design, by Rob Pike
好了,这节课就到这里。如果你觉得今天的内容对你有所帮助,欢迎你帮我分享给更多人。
分享给需要的人,Ta购买本课程,你将得29元
生成海报并分享
赞 29
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
108 | Go 编程模式:错误处理
下一篇
110 | Go编程模式:委托和反转控制
精选留言(17)
- 汪辉2021-01-19之前看到mq的初始化可选配置的时候有用到Functional Options这个模式,没想到在这里找到源头了。7
- Geek_a754be2021-02-09之前在公司自研的微服务框架里面看到大规模使用,原来有个学名叫Functional Options共 1 条评论6
- 萧2021-02-18太强了,受益匪浅3
- 辰星2022-12-04 来自广东太强了
- 拉欧2022-04-14option 意味选项,本身就有函数的意思
- Geek_Huahui2022-03-14真的牛逼
- 今年也没有猫2022-02-04简单理解 就是一种闭包的组织形式。
- 方勇(gopher)2021-12-17确实很多中间件的传参都这么设计,有时候可能要考虑,函数放在client端,还是server端
- 青阳2021-11-12和函数科里化是一回事吗
- 图个啥呢2021-06-25厉害了!
- 👻 小二2021-06-10这种接口也很友好, 就是苦了作者 ```go package main import ( "crypto/tls" "time" ) type Option struct { Timeout *time.Duration TLS *tls.Config } func (option *Option) SetTimeOut(timeout time.Duration) *Option { option.Timeout = &timeout return option } func (option *Option) SetTLS(tls *tls.Config) *Option { option.TLS = tls return option } func MergeOptions(options ...*Option) *Option { //把option合并起来 return nil } type Server struct { Addr string Port int Timeout time.Duration TLS *tls.Config } func NewOptions() *Option { return new(Option) } func NewServer(addr string, port int, options ...*Option) (*Server, error) { srv := Server{ Addr: addr, Port: port, Timeout: 30 * time.Second, TLS: nil, } op := MergeOptions(options...) if op.Timeout != nil { srv.Timeout = *op.Timeout } if op.TLS != nil { srv.TLS = op.TLS } //... return &srv, nil } func main() { _, _ = NewServer("127.0.0.1", 80) _, _ = NewServer("127.0.0.1", 80, NewOptions().SetTimeOut(100)) _, _ = NewServer("127.0.0.1", 80, NewOptions().SetTimeOut(100).SetTLS(nil)) } ```展开
- 轻飘飘过2021-05-20对比js的...解构和函数式编程的compose?
- astrosta2021-04-24用了很久,才知道叫Functional Options
- Geek_450b7e2021-04-23优秀,学到了
- Eirture2021-04-18在 Kubernetes 源代码中学过这一招,原来是叫 Functional Options,有了名字更容易记住和传播 👍
- 小丢👣2021-03-19豁然开朗,respect
- 侯鹏₁₈₆₁₄₀₉₂...2021-02-08非常棒👍🏻