Many of you will have seen the following code snippet while developing or studying the source code
Type Option func(opts *Options) // create a new server, client, pool func NewXXX(name string, opts... Option) {}
When I first switched from Java to Go, I was really overwhelmed by the code. I didn’t understand what these options were and why they were needed
I didn’t know until later that this is called Functional Options.
The functional choice isGolang
A way to implement a clean API in
Not all attributes of a struct are required when building a struct using the newXXX function. These non-essential attributes can be used to make the API cleaner when building a struct by means of functional options
If you want to implement a coroutine pool GPool where the required properties are size of the number of coroutines and the options are: async or not, errorHandler, and maxTaskNums, then the struct design should look like this
Package pool type Option func(options * options) // GPool type GPool struct {size int64 // number of coroutines Options * options} type errorHandler func(err error) // Options: type options struct {async bool // // Support asynchronous submission of task handler } // newgPool func newgPool (size int64, opts... Option) *GPool { options := loadOpts(opts) return &GPool{ size: size, options: options, } } func loadOpts(opts []Option) *Options { options := &Options{} for _, opt := range opts { opt(options) } return options } func WithAsync(async bool) Option { return func(options *Options) { options.async = async } } func WithErrorHandler(handler ErrorHandler) Option { return func(options *Options) { options.handler = handler } } func WithMaxTasks(maxTasks int64) Option { return func(options *Options) { options.maxTasks = maxTasks } }
If you want to create a pool of coroutines and the number of coroutines is 100, just write like this
p := pool.NewGPool(100)
If you want to create a pool of coroutines with 100 coroutines and asynchronous commit support, just write like this
p := pool.NewGPool(100, pool.WithAsync(true))
If you need a pool of coroutines, 100 coroutines, asynchronous commit support, and custom error handling callback, just write like this
P := pool.newgPool (100, pool.withAsync (true), pool.withErrorHandler (func(Err error) {// handle error that occurred during task execution),),
Doesn’t this feel more concise?
What else can we do instead of using functional options?
The first is to build the struct directly, but with a very, very large number of attributes to fill in, which is not very friendly to callers
func NewGPool(size int64, async bool, handler ErrorHandler, maxTasks int64) *GPool {
return &GPool{
size: size,
options: &Options{
async: async,
handler: handler,
maxTasks: maxTasks,
},
}
}
As the number of attributes in a struct grows, this long function signature becomes a nightmare for the caller
The second way is to use the builder mode
func (builder *GPoolBuilder) Builder(size int64) *GPoolBuilder { return &GPoolBuilder{p: &GPool{ size: size, options: &Options{}, }} } func (builder *GPoolBuilder) WithAsync(async bool) *GPoolBuilder { builder.p.options.async = async return builder } func (builder *GPoolBuilder) Build() *GPool { return builder.p }
Callers are comfortable with the API encapsulated by the builder pattern
builder := GPoolBuilder{}
p := builder.Builder(100).WithAsync(true).Build()
However, it is necessary to maintain an additional code belonging to Builder, although the use of simple, but with a certain maintenance cost!!
Overall, the functional option is still the best choice, allowing developers to build clean, friendly APIs.
This article by the blog multiple platform
OpenWriteRelease!