Looking back, I find object-oriented programming in languages like Java and C++ to be incredibly confusing. There are so many special rules and things to know. By contrast, Go gives me tools for everything I want to do, and it’s so simple and explicit.
Sometimes I get so used to the feeling of simplicity that I forget to use some of the nicer features that can save time and increase productivity. “Inheritance” is one of those. This is a story about how a duplicated type definition came to be and the solution for it.
The story begins with a type defined for an API call, where the server received a type from a client and decoded it from JSON.
type incoming struct { field1 string field2 int }
We wanted to do some extra processing with this data in the API server, which required carrying along little bits of decorative data. Originally this was harmless-looking local variables:
for ... { var ( decorator1 bool decorator2 string ) // do some stuff with the struct and the local decorator1 variable }
But when the code started to evolve into something more complex, we ended up keeping around a map of this ancillary data, keyed by the struct it was associated with:
type decorators struct { decorator1 bool decorator2 string } decorators := make(map[incoming]decorators)
At some point it became clear that the struct and the two decorator fields really belonged together, and we were doing it the hard way. We wanted to keep all the data – the data from the JSON, plus these little helper variables – in one value. How should we do this?
In object-oriented languages, you’d probably derive/inherit from the type to create a new type with more data members.
type incomingDecorated struct extends incoming { decorator1 bool decorator2 string }
You can’t do that in Go, because it doesn’t have inheritance. The initial solution was to create an equivalent type by copy-pasting the struct definition and adding more fields to it. This worked fine – the struct could be used for decoding the JSON and then sent around with its extra bits for the processing required.
type incomingDecorated struct { field1 string field2 int decorator1 bool decorator2 string }
But this always had a bit of code smell. Any time a type definition is duplicated, there’s a code smell. Suddenly, we remembered struct embedding. In Go, you can just define a type that contains the original type, plus extra struct fields.
type incomingDecorated struct { incoming decorator1 bool decorator2 string }
This is simple and clear. It keeps the incoming data together with its little intermediate results. The incoming data is still the same type, so it doesn’t need to be cast or assigned piece by piece to send it off to other function calls or so on:
var dec incomingDecorated // decode the JSON into dec // later: doSomething(dec.incoming) // voila! the original type and value, unaltered
Once again (as always), Go provided an elegant solution to a problem I would previously have solved by pounding square pegs into round holes (e.g. abusing inheritance).