I’m learning Go and am wondering why we still need structs and interfaces when function types seem to provide a cleaner and more flexible solution. In Go, we can pass functions as arguments, which allows for behavior abstraction and can achieve many of the same things as structs and interfaces—like polymorphism and organizing code—without the complexity.
I understand that when state management is required or there’s an interface with multiple functionalities rather than one, structs are a better fit, but outside of that, why would we use structs and interfaces? Isn’t using function types for behavior more efficient, especially for mocking and testing? Also, I’ve heard Go is more functional than object-oriented, and I assume this refers to this kind of approach. Is that correct?
Between the following three approaches, which do you think is better?
Using interfaces for abstraction and structs for implementation
Using interfaces for abstraction and functions for implementation
Using function types for abstraction and and functions for implementation
There are lots of scenarios which warrant structs and interfaces, I will list some that come to mind:
Mutable Data: There is often a group of related data at the core of many operations. While this could be represented by local variables in a function closure, it is far easier to use a struct to represent this data.
Re-usability/Extensibility: Interfaces and public fields in structs are a open contract, which can be extended and used by other code. One can easily combine multiple structs or extend types with new methods/interfaces.
Code always lives in functions, so you will always use functions for implementation (instance methods are in essence just functions with a type-switch on its first argument)
And interfaces are just a grouping and naming of multiple function signatures. It is just a more convenient way to say: “I want a ‘thing’ which supports the following methods…”.
Just passing functions around would be very cumbersome. How would you group multiple functions? Into a single dispatcher function with a switch-parameter to know which function to execute? You would be re-inventing interfaces in a worse way.