I changed the stack recently. I switched from .NET to Golang. Leraning new language and the stack brings many gotchas. I’ll write about them as I face them on my journey, especially like OOP guy coming from .NET
When designing a library I use to define a role interface representing a dependency. I’d like to separate it due to several reasons: separation of concerns, shaping the design of structs and simplifying the testing.
When putting it together, I wrote a class (in Golang in the form of a struct type) and defined the “receiver” style functions conforming interface API, example:
That’s all. Golang compiler verifies that my functions meets behaviour the interface defines, enforced by line 18.
Otherwise the error would be:
I’d like to be more explicit in a style of OOP so that I define a class and express that the class implements the interface.
Golang supports the concept called “embedded types”. I decided to improve my design and use it.
When using embedded types, the Go compiler promotes all functions defined on the embedded interface, in this case Shape to the received type Square. So in our case, the function Area() int defined on the interface Shape is propagated to Square without any explicit declaration.
The compilation succeeds. This means that it is possible to call square.Area() method. But it results in the runtime error. Great! In order to fix it I still need to define all the receiver-style methods as before and I’m done. Simple? Clean? No so much. Actually it gets more confusing.
Reason: it is also possible to call square.Shape.Area().
The result is “panic”. What’s more confusing, I could do the following:
The end result is that I misused the embedded types towards the inheritance. They are quite different.
TLDR: using the embedded type is actually a composition with a sugar of promoting the behaviour.
In addition the promoting more embedded types with the same method signature results in the compiler exception.
I definitely misused the embedded types and misunderstood them. Actually, I found out several forums discussing same confusion.
I’m still looking for the usecases where using the embedded types is beneficial, for example:
- mocking, e.g. using testify but that’s for next post.
- composition of the inner types
Back to the inheritance
So I returned back to the basics. In order to express that the struct implements the interface I just do the following:
type Shape interface { | |
Area() int | |
} | |
type Square struct { | |
a int | |
} | |
func NewSquare(a int) *Square { | |
return &Square{a: a} | |
} | |
func (s *Square) Area() int { | |
return s.a * s.a | |
} | |
var _ Shape = new(Square) |
See the line #17. This enforces compiler to check that the square implements Shape interface.
That’s all for now folks!