Whenever we are writing a real world software with a number of features, the core idea we all stick to is writing a modular code i.e. writing modules / libraries which can be seamlessly integrated and used, without too much of hassle. One of the design pattern which enables us to do this in Go is Options pattern.
Why is it needed?
Let’s say we are writing a http server with some configuration - id, max connection and tls.
The first things that comes to mind is creating a constructor function and passing the config to it.
In this case, as the number of arguments increase the more tedious it becomes to maintain the code.
What can be the other solution? Let’s say we create a Options struct and pass it to the constructor which can be used to initialise the server.
This seems to be working, but the issue is if I want to initialise the server with some default values, I can’t. Because
I always have to pass the default values as the options because the server variable is dependent on it. Options pattern helps in making implementation more flexible.
How to implement it ?
Options Pattern 🚀🚀🚀
For implementing options pattern:
We’ve kept the Server struct, but removed the ServerOpts struct entirely.
We’ve defined an Option type, which is a function that takes a pointer to a Server and modifies it.
The NewServer function now takes a variadic parameter of Option functions. It creates a server with default values and then applies each option.
We’ve created separate functions for each option (WithID, WithMaxConn, WithTLS). Each of these returns an Option function that modifies the specific field of the Server.
Now if I want to use a server with default values I can just use it like this:
Or If I want to override the default values then I can use it like :
This approach offers several advantages:
Flexibility: You can specify only the options you want to customize. If you’re happy with the default values for some fields, you don’t need to specify them.
Readability: The code is self-documenting. It’s clear what each option does.
Extensibility: If you need to add new options in the future, you can do so without changing the NewServer function or breaking existing code.
Default Values: It’s easy to provide sensible defaults that can be overridden when needed.
Immutability: If you make the fields of Server private (lowercase), you can ensure they’re only set during creation, promoting immutability.