Published on

Practical Go: Functional Options Pattern

Authors

Go by default not support set default and optional argument in a function. The simple approach is using variadic argument but only support 1 data type for example func createSomething(input1, input2variadic...string)

To supporting multiple type argument in a function you can use functional options pattern, basically variadic argument with type function. The goals is like code example below

main.go
server.go

_17
package main
_17
_17
import (
_17
"log"
_17
_17
"github.com/xx/yy/api"
_17
)
_17
_17
func main() {
_17
srv := api.NewServer(
_17
server.WithHost("lumochift.org"),
_17
server.WithPort(3000),
_17
)
_17
if err := srv.Start(); err != nil {
_17
log.Fatal(err)
_17
}
_17
}

Using functional options pattern you can select the necessary options only, even without argument (server.New()) the struct creation is valid.

The main problem, if the struct is big we must implement all optional function. Fortunately I already build simple tools for generate optional function.

Install on your system using command go install github.com/lumochift/optgen@latest.

Let's expand Server struct above with additional config timeout and maxConn.

server.go

_6
type Server struct {
_6
host string `opt`
_6
port int `opt`
_6
timeout time.Duration
_6
maxConn int
_6
}

Then generate functional options implementation using command optgen -file server.go -name Server -w . Don't forget use -w to append implementation to the selected file server.go. The result only generate function SetHost and SetPort only because declared opt in the struct tag just for host and port.

server.go

_30
_30
type Server struct {
_30
host string `opt`
_30
port int `opt`
_30
timeout time.Duration
_30
maxConn int
_30
}
_30
_30
func NewServer(options ...func(*Server)) (*Server, error) {
_30
_30
server := &Server{}
_30
_30
for _, option := range options {
_30
option(server)
_30
}
_30
_30
return server, nil
_30
}
_30
_30
func SetHost(host string) func(*Server) {
_30
return func(c *Server) {
_30
c.host = host
_30
}
_30
}
_30
_30
func SetPort(port int) func(*Server) {
_30
return func(c *Server) {
_30
c.port = port
_30
}
_30
}

To generate all fields as optional parameter use flag -all for example optgen -file server.go -name Server -w -all, previous generated code must be deleted manually 1

server.go

_52
type Server struct {
_52
host string `opt`
_52
port int `opt`
_52
timeout time.Duration
_52
maxConn int
_52
}
_52
_52
_52
// NewServer returns a new Server.
_52
func NewServer(options ...func(*Server)) (*Server, error) {
_52
_52
// Prepare a Server with default host.
_52
server := &Server{}
_52
_52
// Apply options.
_52
for _, option := range options {
_52
option(server)
_52
}
_52
_52
// Do anything here
_52
_52
return server, nil
_52
}
_52
_52
_52
// SetHost sets the Host
_52
func SetHost(host string) func(*Server) {
_52
return func(c *Server) {
_52
c.host = host
_52
}
_52
}
_52
_52
// SetPort sets the Port
_52
func SetPort(port int) func(*Server) {
_52
return func(c *Server) {
_52
c.port = port
_52
}
_52
}
_52
_52
// SetTimeout sets the Timeout
_52
func SetTimeout(timeout time.Duration) func(*Server) {
_52
return func(c *Server) {
_52
c.timeout = timeout
_52
}
_52
}
_52
_52
// SetMaxConn sets the MaxConn
_52
func SetMaxConn(maxconn int) func(*Server) {
_52
return func(c *Server) {
_52
c.maxConn = maxconn
_52
}
_52
}

Footnotes

  1. The limitation optgen CLI only append code, not replace previously generated code.