Functional Options Pattern

If the function has too many arguments or arguments are growing or the they change often, what is the appropriate way to deal with it?

Long Parameter List

This approach is used when the caller needs to consider all parameters carefully.

If there are too many positional arguments, the same type can be hard to track, you can create named type for type-safe, for example:

1
2
type UserID int64
type ProductID int64

Wrapped in Struct

Pros:

  1. Backward compatibility, the addition or removal of any field doesn’t alter the signature of the constructor.
  2. If the same set of parameters are passing around to lots of functions.
  3. Easier in unittest and mock.
  4. Can use method chaining to initialize parameters.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Request struct {
// Private fields
userID UserID
discountApplied bool
autoPayment bool
}

func (pr Request) ApplyDiscount() Request {
pr.discountApplied = true
return pr
}

func (pr Request) EnableAutoPayment() Request {
pr.autoPayment = true
return pr
}

Cons:

  1. The parameters are easy to slip without paying attention, need to handle the zero values or using zero value validators.

Functional Options Pattern

Refer: https://golang.cafe/blog/golang-functional-options-pattern.html

A pattern of structuring your structs in Go by designing a very expressive and flexible set of APIs that will help with the configuration and initialisation of your struct.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package server

type Server {
host string
port int
}

func New(options ...func(*Server)) *Server {
svr := &Server{}
for _, o := range options {
o(svr)
}
return svr
}

func WithHost(host string) func(*Server) {
return func(s *Server) {
s.host = host
}
}

func WithPort(port int) func(*Server) {
return func(s *Server) {
s.port = port
}
}

func (s *Server) Start() error {
// todo
}

Use it in client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"log"
"github.com/example/server"
)

func main() {
svr := server.New(
server.WithHost("localhost"),
server.WithPort(8080),
)
if err := svr.Start(); err != nil {
log.Fatal(err)
}
}
0%