Go Functional Options
Go typically uses structs to encapsulate data. Optional, customizable behavior makes constructors hard to maintain. The Functional Options Pattern creates objects with optional parameters.
The Basics
A Functional Option is a function that takes an object that needs to be customized and modifies it as needed. Here’s a basic example:
type Option func(*MyStruct)
func NewMyStruct(options ...Option) *MyStruct {
obj := &MyStruct{}
for _, opt := range options {
opt(obj)
}
return obj
}In this example, the NewMyStruct function accepts any number of Option functions. It creates a new MyStruct object and iterates through each option function, passing the new object as an argument.
How to Use Functional Options
A concrete example. Suppose a Server struct has configurable properties:
type Server struct {
Address string
Port int
}
func NewServer(options ...func(*Server)) *Server {
s := &Server{
Address: "0.0.0.0",
Port: 8080,
}
for _, option := range options {
option(s)
}
return s
}Define functions that modify Server defaults:
func WithAddress(address string) func(*Server) {
return func(s *Server) {
s.Address = address
}
}
func WithPort(port int) func(*Server) {
return func(s *Server) {
s.Port = port
}
}Then use those functions to create new Server objects:
s := NewServer(
WithAddress("127.0.0.1"),
WithPort(8081),
)Benefits of Functional Options
- Conciseness: Each configuration option becomes a standalone function, which takes the struct it needs to modify. This eliminates the need for long argument lists, improving code readability.
- Flexibility: Adding new options simply requires creating new functions, without modifying the existing code. New options do not require changing existing code.
- Testability: It’s easy to isolate and test each configuration option, ensuring the robustness of the configuration.
- Readability: Function names can be self-documenting, enhancing comprehension and maintainability of the code.
Specifically, the Golang Functional Options pattern can solve the following problems:
- Verbose Configuration Code: Traditional constructor methods result in lengthy, hard-to-maintain code. The Golang Functional Options pattern breaks configuration into independent functions, making it cleaner and more readable.
- Inflexible Configuration: Adding options the constructor way means modifying existing code. Functional Options add new functions instead.
- Difficult to Test Configuration: Traditional constructor methods mix configuration code with business logic code, which increases the complexity of testing. The Golang Functional Options pattern separates configuration code from business logic code, making it easier to test.
When to Use Functional Options
Consider the functional options pattern when the project involves:
- A struct with numerous configuration options.
- An expectation of flexibility and extensibility in configuration.
- A preference for concise, readable, and maintainable code.
Conclusion
The Functional Options Pattern is a powerful way to create objects with configurable options in Go. The pattern yields code that is concise, easy to extend, and easy to read.