In go, it’s really useful pattern to pass options to functions. It’s a common usecase to have optional arguments to a function. I use it mostly to initialize something with default values and optionally override them.
Primary idea is to use variadic arguments to pass options to a function.
eg:
func NewService(options ...ServiceOption) *Service {
...
}
options ...ServiceOption
is the variadic argument.
There are two ways to do this.
- Using a struct with all the options as a field.
- Using functions with each option value as an argument.
Using a struct to pass options
Example usage
package main
type ServiceConfig struct {
Color string
Size int
Type string
}
func main() {
// Initialize with default values
s := NewService()
// Override some of the default values
s = NewService(&ServiceConfig{
Color: "red",
Size: 10,
})
}
How to setup this options pattern
You can use config...
to accept any number of args in a function. Let’s create a config section with init config function.
// file: config.go
package main
type ServiceConfig struct {
Color string
Size int
Type string
}
// initConfig initializes the config with default values
// or overrides them with the passed config
func initConfig(config ...*ServiceConfig) *ServiceConfig {
var configVals *ServiceConfig
if len(config) > 0 {
configVals = config[0]
} else {
configVals = &ServiceConfig{}
}
if configVals.Color == "" {
configVals.Color = "blue"
}
if configVals.Size == 0 {
configVals.Size = 5
}
if configVals.Type == "" {
configVals.Type = "default"
}
return configVals
}
You can use this function to initialize the config with default values or override them with the passed config. as follows.
// file: service.go
package main
// Service
type Service struct {
config *ServiceConfig
}
// NewService initializes a new service with default values
func NewService(config ...*ServiceConfig) *Service {
serviceConfig := initConfig(config...)
return &Service{
config: serviceConfig,
}
}
func (s *Service) GetOutput() string {
return fmt.Sprintf("Color: %s, Size: %d, Type: %s", s.config.Color, s.config.Size, s.config.Type)
}
and finally, you can use the service as follows.
// file: main.go
package main
import "fmt"
func main() {
// Initialize with default values
s1 := NewService()
fmt.Println(s1.GetOutput())
// Output:
// Color: blue, Size: 5, Type: default
// Override some of the default values
s2 = NewService(&ServiceConfig{
Color: "red",
Size: 10,
})
fmt.Println(s2.GetOutput())
// Output:
// Color: red, Size: 10, Type: default
}
Using functions to pass options
Example usage
package main
func main() {
// Initialize with default values
s := NewService()
// Override some of the default values
s = NewService(
WithColor("red"),
WithSize(10),
)
}
How to setup this options pattern
// file: options.go
package main
type ServiceConfig struct {
Color string
Size int
Type string
}
type ServiceOption func(*ServiceConfig)
// functions to override the config values
func WithColor(color string) ServiceOption {
return func(config *ServiceConfig) {
config.Color = color
}
}
func WithSize(size int) ServiceOption {
return func(config *ServiceConfig) {
config.Size = size
}
}
func WithType(t string) ServiceOption {
return func(config *ServiceConfig) {
config.Type = t
}
}
You can use this function to initialize the config with default values or override them with the passed config. as follows.
// file: service.go
package main
// Service
type Service struct {
config *ServiceConfig
}
// NewService initializes a new service with default values
func NewService(options ...ServiceOption) *Service {
// default values
serviceConfig := &ServiceConfig{
Color: "blue",
Size: 5,
Type: "default",
}
// override with passed options
for _, option := range options {
option(serviceConfig)
}
return &Service{
config: serviceConfig,
}
}
func (s *Service) GetOutput() string {
return fmt.Sprintf("Color: %s, Size: %d, Type: %s", s.config.Color, s.config.Size, s.config.Type)
}
and finally, you can use the service as follows.
// file: main.go
package main
import "fmt"
func main() {
// Initialize with default values
s1 := NewService()
fmt.Println(s1.GetOutput())
// Output:
// Color: , Size: 0, Type:
// Override some of the default values
s2 = NewService(
WithColor("red"),
WithSize(10),
)
fmt.Println(s2.GetOutput())
// Output:
// Color: red, Size: 10, Type:
}
There you go, two ways to use options pattern in go. Enjoy.