At the time of writing this, I am not aware of any problem this solves in idiomatic Go code. But I thought of it, so I am sharing it with you. Now, you can use it in your own code, and have a new problem.
Go’s solution for polymorphism is interfaces. An interface is essentially a collection of methods. If a type implements those methods - it matches the interface, and can passed to functions that expect that interface. Note that you don’t need to explicitly specify the interface you’re implementing - if your type has the right methods, it’ll match. Be it intentionally or by accident.
When you create your type, it’s likely you’ll have more methods than those that match specific interface.
Sure, your dog implements the Eater
interface, but it also has Bark
and Bite
methods.
Makes perfect sense.
But some time in the future, the library exposing Eater
adds a new interface - VenomousBiter
- which requires the Bite
method.
Now your Dog
matches VenomousBiter
as well.
Unfortunately, the author of the library intended VenomousBiter
to only be used for venomous animals (and Snakebite
as a Snake
method seemed all too specific), and now your dog is venomous.
Better design and naming of interfaces and methods can probably protect you from this mess.
But can you really trust everyone to be sensible enough, or would you rather put in some work to avoid venomous dogs?
The problem stems from Go’s interfaces being a form of structural subtyping ,matching based on the structure of a type - it’s methods, this case; as opposed to nominal subtyping, where types are matched based on their names (think OOP inheritance). To fix it, we’ll tag our interfaces, ensuring only types with matching tags match the interface.
To make tags work, we need to make sure they cannot be added by accident. The presence of a tag must always indicate intent. Interfaces only check for methods - so we need to add a method that cannot be matched by accident. Since any method name can be matched by accident, we’ll use something stronger - package boundaries. By using a non-exported method name in the interface, we can guarantee that no type outside the current package can match the interface.
|
|
Great.
Now our dog is no longer venomous.
On the other hand, we have no way to make our VenomousSnake
venomous.
So we introduce another type in the library - the other half of the tag!
|
|
And use that for our snake:
|
|
Now, our snake is venomous, and our dog is not. Mission accomplished.
Full Code
You can read the full code below, or run it in the Go Playground
|
|
|
|