In last post we learned how we can use functional options to pass parameter to our functions. In this post we’ll see an alternative way to do the same thing.
I’ll take the same code from the last post to keep things simple.
Variadic Functions for Options
line 1-5 we define type Coffee (or class if you would like to say). This is the analogy our code is build upon because I like coffee whenever I can.
7-15 we define our New function.
func(Coffee) Coffee says New takes a function with Coffee object and spits out a Coffee Object. The
... part tells it can take many of these functions. Yes, there’s no limit. Then New function itself returns a Coffee object.
In the body of the function, we create a default instance of Coffee. Then run all the functions one by one. As all the function return same modified Coffee object, we can modify it an many time the number of function are passed. At last return that object.
Now for every option we want to create, we can create a function with the same signature we saw above, i.e.,
func(Coffee) Coffee. I have created two such functions between. There can be any logic you want. just make sure to return the modified coffee object.
line 46, we define a New coffee object with
WithSugar parameter. The logic of the function will modify Coffee to turn
Sugar field to be true.
Prepare() prints out whatever fields are set to be true.
Now let’s do the same thing with Options struct in next section.
Options Struct for Options
1. Variadic functions are gone
That seems like a save of real estate. Because every function takes a fare amount of space. Also there are less moving parts to work with.
WithSteamedMilk are completely gone. Along with the signature of the New function, which is changed from being
New(opts ...func(Coffee) Coffee) Coffee to
New(opts Options) Coffee. Pretty concise, eh?
2. A new Options struct is introduced
All the settings which could have been there in the form of functions are listed directly here. Functions were just a way to encapsulate the settings.
You must be wondering, can’t we modify all the fields in Coffee directly? Do we need a separate Options struct for this thing? That’s an extra step!
Note that all the fields in Coffee struct could have been unexported. And if you have called this API from your driver code, you won’t be able to set it directly, as it is unexported. Consider this case:
You might get an error like this when run:
# command-line-arguments ./main.go:10:21: unknown field 'coffeeBeans' in struct literal of type coffee.Coffee
coffeeBeans is an unexported field. The idea here I’m trying to put is, encapsulated inside, while only interface to modify it being the Option struct.
The struct method also gives freedom to set default values. As you can see on
line 13-22, I have set CoffeeBeans to true, no matter what you pass to it.
3. New function accepts an Option
Instead of taking functions, like before, we now create an option struct beforehand. And pass it New function.
New is also modified to generate a new Coffee object with all the options passed into it. Here we can also make some behaviors default. Like in the above example, even if the user passes CoffeeBeans value as false, our function will always create a Coffee object with CoffeeBeans.
I haven’t yet form any solid opinion with any of them. Both of them seem equally reasonable to use. I need to practice more and more in order to have a solid opinion on any of them.
Which one do you like any why? Let me know in the comments.