4 ms·
But with Go's system you can't provide an implementation of an interface for a type unless you defined that type in your package. You have to use an adapter lay
by pcwalton 10y ago
But with Go's system you can't provide an implementation of an interface for a type unless you defined that type in your package. You have to use an adapter layer for that. (For example, say I want to make an interface with a function "func Show() string" that prints debug information about a type--I can't implement that function directly on http.Client, only on an e.g. "type MyClient = http.Client" wrapper.) So that makes it harder to achieve loose coupling, because you can't make your own interfaces that unify standard library types and your types unless you make your types take exactly the same methods with exactly the same signatures (problematic if you want your types to support slightly different functionality, etc.)
In a system that allows you to implement interfaces for types that you didn't define in your package, there is a little more boilerplate necessary compared to Go, but only a few lines necessary to achieve the exact same effect. In pseudo-Go:
type Do interface {
func Do(req *Request) (*Response, error)
}
// boilerplate
implement Do for http.Client {
func (c *Client) Do(req *Request) (*Response, error) {
return c.Do(req) // call stdlib implementation
}
}
// custom implementation of Do
implement Do for MockClient { ... }
So Go makes a tradeoff with its structural typing: it doesn't require the small amount of boilerplate code to achieve the pattern you describe, but it prevents you from providing new implementations for types you didn't define. Reasonable people can differ as to whether the side of the tradeoff Go makes encourages or discourages loose coupling.
- n0w 10y agoI think you're a little off. The only boilerplate required for your example is the interface definition. After that you can define your own implementations of the "Do" interface as suggested by the parent. // boilerplate type Do interface { func Do(*http.Request) (*http.Response, error) } // Custom Implementation type MockClient struct {} func (mc *MockClient) Do(req *http.Request) (*http.Response, error) { // MockClient type now satisfies the Do interface }
- masklinn 10y agoYou're not taking this issue in account: > you can't make your own interfaces that unify standard library types and your types unless you make your types take exactly the same methods with exactly the same signatures (problematic if you want your types to support slightly different functionality, etc.)
- n0w 10y agoThe original comment mentioned being able to decouple Go code from specific implementations of functionality by relying on self defined interfaces instead of specific functions. pcwalton's example seemed to confuse this point, which I wanted to clarify. As to that point, your interface types will only match standard library ones if you're trying to unify them. In which case you probably want them to have the same signature.
- masklinn 10y ago> The original comment mentioned being able to decouple Go code from specific implementations of functionality by relying on self defined interfaces instead of specific functions. pcwalton's example seemed to confuse this point, which I wanted to clarify. The problem is that you're clarifying the wrong thing, the example is confusing because it trivially proxies a built-in method but the remark is what's important to the reply. Your reply is only confusing things further by considering the flawed example but ignoring the actual point. > As to that point, your interface types will only match standard library ones if you're trying to unify them. No, why would you think that? You may want to "compatibilise" multiple third-party types (that some of them are in the standard library is a complete red herring) as well as first-party ones (a mock or whatever) e.g. abstract over stdlib sockets, zmq sockets and sqlite.
- n0w 10y agoYou're right, I'm confusing the issue. I re-read pcwalton's comment and I think understand the point they're trying to make. Not sure the last part was correct though. > So Go makes a tradeoff with its structural typing: it doesn't require the small amount of boilerplate code to achieve the pattern you describe, but it prevents you from providing new implementations for types you didn't define. Shouldn't this be "So Go makes a tradeoff with its structural typing: it does require a small amount of boilerplate code to achieve the pattern you describe, but it allows you to provide new implementations for types you define."?
- deleted 10y ago[deleted]
- kasey_junk 10y agoIs there a tradeoff between structural typing and allowing the addition of functions to types defined outside of your package? They seem like orthogonal concerns to me, but maybe they aren't?
- dilap 10y agoIs there any reason Go couldn't allow defining methods on types defined in another package? It seems like you could allow that, gain the property you want, and still have structural typing. The confusing thing about this if you did implement it, is if you import a package B that implements more methods for a type in package A, do those methods magically become available to you? One of the nicest properties about Go currently IMO is that it's always very, very clear where things are defined.