【GO】Functional Options

Time:2022-5-17

In our programming, we often need to configure an object (or business entity). For example, the following business entity (note that this is only an example):

type Server struct {
    Addr     string
    Port     int
    Protocol string
    Timeout  time.Duration
    MaxConns int
    TLS      *tls.Config
}

In this server object, we can see:
IP address to listen onAddrAnd port numberPort, these two configuration options are required (of course, both IP address and port number can have default values. When we use this example to think that there is no default value, and it can’t be empty. They need to be required).
Then there is the agreementProtocolTimeoutandMaxConnsFields, which cannot be empty, but have default values. For example, the protocol is TCP, the timeout is 30 seconds and the maximum number of links is 1024.
One moreTLSThis is a secure link. You need to configure the relevant certificate and private key. This can be empty.

The most common way to solve this problem is to use a configuration object, as shown below:

type Config struct {
    Protocol string
    Timeout  time.Duration
    Maxconns int
    TLS      *tls.Config
}

We move all the non mandatory options to a structure, so the server object becomes:

type Server struct {
    Addr string
    Port int
    Conf *Config
}

Functional Options

First, we define a function type:

type Option func(*Server)

Then, we can define the following set of functions in a functional way:

func Protocol(p string) Option {
    return func(s *Server) {
        s.Protocol = p
    }
}
func Timeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.Timeout = timeout
    }
}
func MaxConns(maxconns int) Option {
    return func(s *Server) {
        s.MaxConns = maxconns
    }
}
func TLS(tls *tls.Config) Option {
    return func(s *Server) {
        s.TLS = tls
    }
}

The above group of codes pass in a parameter and then return a function. The returned function will set its own server parameters. For example:

When we call one of these functions MaxConns(30)Time
Its return value is afunc(s* Server) { s.MaxConns = 30 }Function of.
This is called higher order function. Mathematically, just like this mathematical definition, the formula for calculating the area of a rectangle is:rect(width, height) = width * height; This function requires two parameters. After we wrap it, it can become a formula for calculating the square area:square(width) = rect(width, width) in other words,squre(width)Returns another function, which isrect(w,h) But his two parameters are the same. Namely:f(x) = g(x, x)

OK, now let’s make another oneNewServer()Function, where there is a variable parameteroptionsIt can pass out multiple functions on the above, and then use onefor-loopTo set up our Server Object.

func NewServer(addr string, port int, options ...func(*Server)) (*Server, error) {

  srv := Server{
    Addr:     addr,
    Port:     port,
    Protocol: "tcp",
    Timeout:  30 * time.Second,
    MaxConns: 1000,
    TLS:      nil,
  }
  for _, option := range options {
    option(&srv)
  }
  //...
  return &srv, nil
}

So when we create the server object, we can do this.

s1, _ := NewServer("localhost", 1024)
s2, _ := NewServer("localhost", 2048, Protocol("udp"))
s3, _ := NewServer("0.0.0.0", 8080, Timeout(300*time.Second), MaxConns(1000))

A high degree of neatness and elegance not only solves the problem of useConfigObject mode requires aconfigParameter, but when it is not needed, it is putnil Or put itConfig{}The choice of is difficult, and there is no need to quote oneBuilderThe control object, which uses functional programming directly, is also very elegant in code reading.

Therefore, in the future, when you want to play similar code, it is strongly recommended to use itFunctional OptionsThis approach has at least the following benefits:

  • Intuitive programming
  • Highly configurable
  • Easy to maintain and expand
  • From document
  • It’s easy for newcomers
  • There’s nothing confusing (yes)nil(or empty)

reference material

Go programming mode: functional options