When writing Go code the general guideline is not to create interfaces unless it is really needed. A good example would be an interface with only one concrete implementation. Now for the mind boggling part; in C# we would probably create that interface if the type needs to be faked in some test while in Go you wouldn't. Or actually you would. But in a different place...
Let's assume that you have a package/namespace Foo with a type Bar containing the method Baz(). Then we have another namespace/package Abc with the type Xyz that needs a fake Foo.Bar for some testing. In C# we would create a Foo.IBar and let Abx.Xyz depend on Foo.IBar and we feel at home and things are as we are used to.
In Go however you tend to do things a little differently. Since there is only one implementation of Bar (the fake one doesn't count) a Foo.Bazer interface is not desirable. The solution lies in that any type can satisfy any interface as long the methods match. Hence the solution is to define the interface where it is used!
That is; we would define the interface as Abc.Bazer and have Abc.Xyz depend on that. Now the Abc.Xyz tests can define their own fake of the interface it needs and through go interface magic Foo.Bar satisfies the interface too.
This felt very backwards to me at first as it goes against what I was used to but over time it actually ended up making a lot of sense. Defining interfaces where they are used rather than implemented is a good principle I think.
Just curious: how do you perform refactoring actions such as method renaming when there is no contract (such as an interface)?ReplyDelete
So there is an interface. Another way of describing it is that in Go the interface is a contract on how you want to consume something rather than a contract of how something is produced. In other words declare what you want rather than what you are.Delete
From a refactoring perspective I don't see a difference as if you change the interface but not the concrete type (or vice versa) the code will no longer compile as the concrete implementation can no longer be used where the interface is expected.
But yes this also means that if you have two types (A and B) that both "implement" the Foo interface but in practice only A is used as a Foo, then the compiler does not care that you don't have to change B as it is never used as a Foo.