In the process of developing or learning source code, many of you have probably seen the following code snippet

type Option func(opts *Options)

// Create a server, client, pool, etc
func NewXXX(name string, opts ... Option) {}
Copy the code

When I first switched from Java to GO, I looked at the code in a confused way. I did not understand what the options were and why they were needed

Only later did I learn that this is called Functional Options.

The functional choices areGolangA way to implement a concise API in

When NewXXX function is used to build struct, not all attributes in struct are necessary. These non-essential attributes can be used in the process of building struct through functional options to achieve a more concise API

Suppose you need to implement a coroutine pool GPool, where the necessary attributes include size, the number of coroutines, and the options: async or not, errorHandler, maxTaskNums, then the struct design should be as follows

package pool

// Option defines functional options
type Option func(options *Options)

// GPool coprogram pool
type GPool struct {
	size    int64 // Number of coroutines
	options *Options
}

type ErrorHandler func(err error)

// Options Place optional Options here
type Options struct {
	async    bool         // Whether asynchronous task submission is supported
	handler  ErrorHandler // If the task execution fails, this function is called back
	maxTasks int64        // The maximum number of cache tasks accepted by the coroutine pool
}

// NewGPool Specifies a new pool
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
	}
}
Copy the code

If you want to create a pool of coroutines, 100 coroutines, you just write it like this

p := pool.NewGPool(100)
Copy the code

If you need to create a pool of 100 coroutines that support asynchronous commits, just write this

p := pool.NewGPool(100, pool.WithAsync(true))
Copy the code

If you need to thread a coroutine pool of 100, support asynchronous commits, and call back custom error handling, just write this

p := pool.NewGPool(100,
	pool.WithAsync(true),
	pool.WithErrorHandler(func(err error) {
		// Handle errors that occur during task execution}),)Copy the code

Does it feel more concise to write it this way?

What else can we do if we don’t use functional options?

The first is to build a struct directly, but fill in a very, very large number of attributes, which is not caller-friendly

func NewGPool(size int64, async bool, handler ErrorHandler, maxTasks int64) *GPool {
	return &GPool{
		size:    size,
		options: &Options{
			async:    async,
			handler:  handler,
			maxTasks: maxTasks,
		},
	}
}
Copy the code

As more and more attributes are added to structs, this long function signature can be a nightmare for callers

Second, use the Builder pattern

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(a) *GPool {
	return builder.p
}
Copy the code

Callers are still very comfortable with the API wrapped in the Builder pattern

	builder := GPoolBuilder{}
	p := builder.Builder(100).WithAsync(true).Build()
Copy the code

However, it is necessary to maintain a code belonging to Builder, although the use of simple, but with a certain maintenance cost!!

Overall, the functional option is the best option for developers to build clean, user-friendly apis.