关于go:golang中的选项模式

47次阅读

共计 2766 个字符,预计需要花费 7 分钟才能阅读完成。

索引

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,但能够随便抉择适宜您的域语言的任何名称

咱们的两个选项 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))
}

正文完
 0