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 onAddr
And 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 agreementProtocol
、 Timeout
andMaxConns
Fields, 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 moreTLS
This 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 parameteroptions
It can pass out multiple functions on the above, and then use onefor-loop
To 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 useConfig
Object mode requires aconfig
Parameter, but when it is not needed, it is putnil
Or put itConfig{}
The choice of is difficult, and there is no need to quote oneBuilder
The 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 Options
This 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