golang中的选项模式

索引

https://waterflow.link/articles/1663835071801

当我在使用go-zero时,我看到了好多像下面这样的代码:

...

type (
    // RunOption defines the method to customize a Server.
    RunOption func(*Server)

    // A Server is a http server.
    Server struct {
        ngin   *engine
        router httpx.Router
    }
)

...

// AddRoutes add given routes into the Server.
func (s *Server) AddRoutes(rs []Route, opts ...RouteOption) {
    r := featuredRoutes{
        routes: rs,
    }
    for _, opt := range opts {
        opt(&r)
    }
    s.ngin.addRoutes(r)
}

...

// WithJwt returns a func to enable jwt authentication in given route.
func WithJwt(secret string) RouteOption {
    return func(r *featuredRoutes) {
        validateSecret(secret)
        r.jwt.enabled = true
        r.jwt.secret = secret
    }
}

我们可以把重点放在RouteOption上面。这里就使用了选项模式。

什么是选项模式?

选项模式是一种函数式编程模式,用于为可用于修改其行为的函数提供可选参数。

如果你用过php的话,你肯定会看到过这样的函数,毕竟PHP是世界上最好的语言:

public function cache(callable $callable, $duration = null, $dependency = null)
  
// 我们可以这样调用
cache($callable);

// 也可以把后面的可选参数带上
cache($callable, $duration);

这种能力在 API 设计中非常有用,因为

  • 允许用户以最低配置使用方法,同时仍为有经验的用户提供充足的配置选项。
  • 允许开发者在不破坏向后兼容性的情况下添加新选项。

然而,在 Golang 中,这是不可能的。该语言不提供添加可选参数的方法。

这就是“选项模式”的用武之地。它允许用户在调用方法 时传递其他选项,然后可以相应地修改其行为。让我们看一个小例子。

假设我们有一个结构体Server,它有 3 个属性port, timeoutmaxConnections

type Server struct {
    port           string
    timeout        time.Duration
    maxConnections int
}

然后我们有个Server的工厂方法

func NewServer(port string, timeout time.Duration, maxConnections int) *Server {
    return &Server{
        port:           port,
        timeout:        timeout,
        maxConnections: maxConnections,
    }
}

但是现在我们希望只有端口号是必传的,timeoutmaxConnections成为可选参数。在很多情况下,这些的默认值就足够了。另外,我不想用这些不必要的配置来轰炸刚刚学习或试验我的 方法 的新用户。让我们看看该怎么去实现。

首先我们定义一个新的option结构体

type Option func(*Server)

Option是一个函数类型,它接受指向我们的Server. 这很重要,因为我们将使用这些选项修改我们的Server实例。

现在让我们定义我们的选项。惯例是在我们的选项前面加上 With,但可以随意选择适合您的域语言的任何名称

func WithTimeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

func WithMaxConnections(maxConn int) Option {
    return func(s *Server) {
        s.maxConnections = maxConn
    }
}

我们的两个选项WithTimeoutWithMaxConnections,采用配置值并返回一个Option。这Option只是一个函数,它接受一个指向我们Server对象的指针并将所需的属性设置为提供的值。例如,WithTimeout获取超时持续时间,然后返回一个函数(其签名与 Option相同)将我们服务器的 timeout 属性设置为提供的值。

在这里,我们使用了一种几乎所有现代语言(包括 Golang)都支持的称为闭包的技术

我们的工厂方法Server现在需要修改以支持这种变化

func NewServer(port string, options ...Option) *Server {
    server := &Server{
        port: port,
    }

    for _, option := range options {
        option(server)
    }

    return server
}

现在我们就可以用上面世界上最好的语言的方式调用了

NewServer("8430")
NewServer("8430", WithTimeout(10*time.Second))
NewServer("8430", WithTimeout(10*time.Second), WithMaxConnections(10))

在这里你可以看到我们的客户端现在可以创建一个只有端口的最小服务器,但如果需要也可以自由地提供更多的配置选项。

这种设计具有高度的可扩展性和可维护性,甚至比我们在 PHP 中看到的可选参数还要好。它允许我们添加更多选项,而不会膨胀我们的函数签名,也不会触及我们在工厂方法中的代码。

下面是完整代码:

package main

import "time"

type Option func(*Server)

type Server struct {
    port           string
    timeout        time.Duration
    maxConnections int
}

func NewServer(port string, options ...Option) *Server {
    server := &Server{
        port: port,
    }

    for _, option := range options {
        option(server)
    }

    return server
}

func WithTimeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

func WithMaxConnections(maxConn int) Option {
    return func(s *Server) {
        s.maxConnections = maxConn
    }
}

func main() {
    NewServer("8430")
    NewServer("8430", WithTimeout(10*time.Second))
    NewServer("8430", WithTimeout(10*time.Second), WithMaxConnections(10))
}
15 声望
0 粉丝
0 条评论
推荐阅读
golang中的错误处理
0.1、索引[链接]1、panic当我们执行panic的时候会结束下面的流程: {代码...} {代码...} 但是panic也是可以捕获的,我们可以使用defer和recover实现: {代码...} {代码...} 那什么时候适合panic呢?在 Go 中,pan...

liuyuede阅读 624

封面图
SegmentFault 思否正式开源问答社区软件 Answer
作为国内领先的新一代技术问答社区,SegmentFault 思否团队在社区建设上有着多年积累。Answer 不仅拥有搭建问答平台(Q&A Platform)的基础功能,还在产品设计上融入了开发团队对社区发展的思考,并将其经验产品...

SegmentFault思否29阅读 4.2k评论 14

封面图
Golang 中 []byte 与 string 转换
string 类型和 []byte 类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间的内在联系来拨开迷雾。

机器铃砍菜刀21阅读 54.5k评论 1

年度最佳【golang】map详解
这篇文章主要讲 map 的赋值、删除、查询、扩容的具体执行过程,仍然是从底层的角度展开。结合源码,看完本文一定会彻底明白 map 底层原理。

去去100214阅读 10.9k评论 2

年度最佳【golang】GMP调度详解
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底的. 这篇文章将通过分析...

去去100213阅读 11.1k评论 4

【已结束】SegmentFault 思否技术征文丨浅谈 Go 语言框架
亲爱的开发者们:我们的 11 月技术征文如期而来,这次主题围绕 「 Go 」 语言,欢迎大家来参与分享~征文时间11 月 4 日 - 11 月 27 日 23:5911 月 28 日 18:00 前发布中奖名单参与条件新老思否作者均可参加征文...

SegmentFault思否11阅读 4.6k评论 11

封面图
【Go微服务】开发gRPC总共分三步
之前我也有写过RPC相关的文章:《 Go RPC入门指南:RPC的使用边界在哪里?如何实现跨语言调用?》,详细介绍了RPC是什么,使用边界在哪里?并且用Go和php举例,实现了跨语言调用。不了解RPC的同学建议先读这篇文...

王中阳Go8阅读 3.5k评论 6

封面图
15 声望
0 粉丝
宣传栏