One reason, as Volker so accurately puts it in the comment he left: "Interfaces encapsulate behavior only. Data is not behavior."
Another, far more easy to understand reason would be: testing. Currently, an interface only specifies a set of behaviours. Suppose I've got some code I want to test that expects a Writer
interface. If some ham-fisted dev implemented an interface like this:
type Writer interface {
F *os.File
Write(b []bytes) (int, error)
}
Then I'd need a mock type that has a file pointer (which I might not be able to leave at nil
, because the interface should be usable by the code I call). That's horrible in and of itself. Of course, creating a temp file is easy.
Now how about an interface like this:
type ComplexInterface interface {
Engine *mypkg.SomeComplexType
Client *grpc.Client
// and so on
}
I'd need to create a complex type instance, which in turn might have 20 dependencies... God only knows how expensive a change to any of the interfaces might become. If mypkg.SomeComplexType
changes, then I'd soon find out that tests in another package start breaking, because I've created a very tight coupling.
So yeah: interfaces define behaviour, adding properties introduces a ton of risks (interfaces as pseudo-generics, tight coupling, maintenance hell, ...)
Either way, if you want "properties" in an interface, why not simply write this:
type MyWriter interface {
File() *os.File
Write(b []bytes) (int, error)
}
Substitute a property for a getter. Job done.