This post was featured in our July 2015 anthology of most popular SolarWinds Database Performance Monitor (DPM) blog posts of all time.
One of the patterns I’ve found in the standard Go library is what I call package-and-object. (That’s my own name; maybe I’m reinventing or naming something already known by another name.) You can see this idiom in several packages. It makes these packages a delight to use.
The essence of the idiom is that you design a type with methods as usual, and then you also place matching functions at the package level itself. These functions simply delegate to a default instance of the type that’s a private package-level variable, created in an init() function.
This gives users a lot of flexibility for how they want to use the functionality you’ve designed for the type. If you design carefully, then most users’ needs can be satisfied by the default behavior you provide, and they don’t need to create variables to get that functionality. This saves them from annoyances such as having to choose which scope owns the variable and whether to make it global or to pass it around in function calls as needed. Yet if users need to, they can create their own objects if they want to tweak the default behavior.
To take a look at the default logging functionality, for example, you can just import the log
package and then write things like log.Print(). It’s super-concise and handy, and it does the right thing. Want to customize it? Make your own log.Logger variable, and set its properties. The same goes for the flag
package. You can see this pattern in other places too, such as http.ListenAndServe()
At SolarWinds we’ve used this approach a time or two in our own packages, and found it very handy.
Do you use it? What suggestions do you have?