I find the comparison to java a bit unfair, because this pattern is totally possible but has been largely deemed a bad pattern. The analog to the pattern in java is to extend from a non-final class. So in this case "class HTTPClient" and "class CachedHTTPClient".
There are lots of drawbacks to doing it that way, the worst of which is that you need to remember to override the method in CachedHTTPClient everytime you add a method to HTTPClient, and the compiler gives you no hints about it.
If an extending class does not implement a method which is implemented by its super class, a call to that method on a instance of the extending class will invoke the super class' implementation of the method.
The super class implementation of the method may perform something that is entirely correct even for the extended class, or it may do something that is inconsistent with the assumptions of the extended class. Either way, there is no way for the compiler or runtime to determine if the omission of the method in the extending class is intentional or a mistake.
This is one reason to prefer Composition over Inheritance, especially in Java.
Yes, that usually would work, but it's more error-prone than composition, because there may be calls in the base class to the method you overrode. You could even inadvertently create infinite recursion.
It's important to deeply understand why patterns are deemed bad in one language, so you can see if it applies to another language [1]. In the case of Go, we generally try to keep interfaces small. In many real-world cases of decoration in Go, probably the vast, vast majority, the interface only has one method, so there isn't any way to forget to override the other methods.
While there's no particular exact language feature I can point at to say why this happens, in general, Go interfaces are more fluid since they don't have to be declared up front, and end up being kept simpler than Java classes and interfaces, so the concerns about failing to override other methods are greatly, greatly reduced. They are not technically eliminated, but they're pushed way, way down my list of priorities.
[1]: This is not special pleading for Go, it goes well beyond that. A good design in Java is a bad design in Python, a good design in Python is a bad design in Java, etc. If you had two languages where the exact same patterns were appropriate in the exact same way, I'd question whether you actually had two languages.
This doesn't protect you in the slightest. The failure case is clear and likely for something like an CachedHttpClient where every call should be cached. You're talking hypothetically but here's a simple common failure case.
How do you fail to implement the second method override on a single-method interface?
Bear in mind that when using the interface, the interface is all there is. It essentially erases the other methods from consideration. That's why an interface value in Go is a distinct type; it isn't just "a thing that can happen to hold all these various concrete values", it is a distinct thing with its own method set. So discussion underlying structs and their method sets is a category error. (This is a bit subtle, but important to understand what is actually going on in Go, or any other language with a similar setup.)
I'm not talking hypothetically. I'm talking about what happens in real Go code. Discussing what could happen if people wrote interfaces in a way other than they actually do is what is hypothetical. This is the sort of thing that matters when deciding whether or not a particular pattern is useful in a language. It's rarely entirely down to pure syntax concerns or some sort of Platonic software engineering consideration. In fact, even within the same language you can encounter situations where a pattern makes sense in one framework but is a bad idea in another framework; Javascript is full of such things. (Whether that's for good or bad reasons is a separate consideration; the fact is that it is full of them.)
>How do you fail to implement the second method override on a single-method interface?
Well in the example given, there is an interface Client with two methods. If a maintainer controls the Client interface and the HTTPClient implementation, the case can occur where that maintainer updates Client and HttpeClient. Suddenly, CachedHTTPClient in the downstream project has an unchached method and as far as my limited Go knowledge goes, no compiler error.
>I'm talking about what happens in real Go code
The case appears in the blog post. Would you say the blog is not idiomatic Go?
Yes, it's a contrived example to make the point in the blog. Blog samples have to be taken that way. The vast majority of the time real code decorates in Go, it's with either A: an interface of 1 method or B: something sufficiently local in concern that this sort of thing isn't a concern, beyond it just being a bug (a compiler forcing you to specify an override won't save you from just sticking the minimal stub in). Part of why this can be a problem in Java is you tend to get a certain sprawl to your class hierarchy that doesn't occur in Go. Or most other languages, used well. Java's got some unique weaknesses in this area that do not generally translate.
I personally think it has more to do with the coders than the code. Seeing as it's trivial to do it wrong in Go (see blog) I have to assume IBM websphere Go would be a similar nightmarish hellscape as it is in Java.
Yes I think the snarky 'bad way Java does this' (ie implement the base interface entirely and call into HTTPClient from the outside) is actually a lot safer.
golang is right :) Seriously though, Go doesn't have inheritance and I don't miss it. The only reason I need it in Java, etc is because so many libraries expect/require you to subclass.
It is exactly composition; the compiler just generates the methods to automatically delegate to the anonymous elements. It's just syntax sugar that probably creates more confusion than it alleviates. It's not inheritance of any kind because the outer struct cannot be passed into a function that takes the inner element type (e.g., if you have `type B int` and `type A struct {B}` and `func foo(B) {}`, you can't pass an instance of `A` into `foo()`--`foo(A{B: 0})` is a compiler error.
As it is just composition, it is strictly better than inheritance, but still probably unnecessarily confusing versus just writing out the delegation methods yourself.
>It's not inheritance of any kind because the outer struct cannot be passed into a function that takes the inner element type
My mistake. I was mislead by the blog post.
From the article:
> The new type would be interchangeable with the existing client which would minimize the need for changes to existing code.
So with go exported structs it is not fair to say they can be used interchangeably if at any point that instance is used as a parameter, field, or variable that defines the type?
In the examples given in the article, the thing that is used as parameters is the interface Client not the concrete type HTTPClient. Would that not allow CachedHTTPClient to passed around as if it was a Client and would that not show the same issues as inheritance?
Both types implement the `Client` interface. So either type can be passed into a function that accepts the `Client` interface, but a `CachedHTTPClient` cannot be passed into a function that accepts an `HTTPClient` (you would have to pass the `CachedHTTPClient.HTTPClient`) and of course not vice versa either. You can do the same thing in Java (forgive my syntax):
public interface Client {
ArrayList<String> getUsers();
void createUser(String name);
}
public class HTTPClient implements Client {
public ArrayList<String> getUsers() { /* ... */ }
public void createUser(String name) { /* ... */ }
}
public class CachedHTTPClient implements Client {
private HTTPClient httpClient;
public ArrayList<String> getUsers() { /* ... */ }
// In Go via "struct embedding", this method would be generated
// automatically; in Java, we have to write it out. NBD.
public void createUser(String name) { this.httpClient.createUser(name); }
}
Makes sense. I guess I just don't see how exported types save you from any of the pitfalls of inheritance. In my mind composition is the use of existing types without being forced into a type contract.
Mind you, I don’t advocate for struct embedding, but it doesn’t force you into a type contract. The outer struct retains its type—it is not a subtype of the nested type so it can’t be used in places that take the inner type.
Literally all it does is automatically create methods on the outer struct that delegate to the anonymous member.
Unlike inheritance, there is no fragile base class problem and methods on the inner anonymous member can’t dispatch to methods on the outer struct. Also, the “parent” member is just another field in your struct. You can modify it or replace it at runtime, unlike the parent in OOP languages.
This is a bad practice in Go for any interface type. Interfaces which are wrapped will fail all type assertions against their original type and fail to assert into additional interfaces that the original also implemented.
For example, imagine you have an io.Reader. It may actually implement io.ReaderTo which is used in some cases to implement alloc-free copying. If you wrap it into a new struct, e.g. ioutil.NopCloser you delegate the main interface but not the bonus interfaces. You can no longer cast to the type of the original reader interface either (concrete type comparison is more important with errors, which had a custom fix in Go just for this)
If we have an io.Reader which implements io.ReaderTo then it has a certain set of exported functions. If this is embedded in ioutil.NopCloser then all those exported functions will be available on ioutil.NopCloser. Wouldn't structural typing then ensure that ioutil.NopCloser implements io.ReaderTo as well as io.Reader?
In this example, by wrapping an interface in another interface, you lose the ability to cast back down to an interface that the original type fulfilled.
This is often a reasonable thing to do (encapsulation). Callers don't have a right to private details, and I would go further and say that an interface value's concrete type is itself an implementation detail that callers shouldn't depend on unless the interface's documentation declares it stable.
This might be a bad practice, but I don't feel very bad if you subvert the type system like this and it bites you. If `io.ReaderTo` is important, it should be a bonafide first-class method, not something that's determined and dispatched to via reflection.
This is the way that “optional” methods work in Go. For example, go decided not to rebuild the entire HTTP library to support web sockets. Instead, supported servers will pass an http.Request that also implements http.Hijacker for letting a request stay alive after the Handler closure
I get it, but I disagree with subverting the type system. The type system is there to promote safety and maintainability; you shouldn't subvert it even if "you" are the std lib.
Embedding like this will only lead to pain long term. It's preferable to implement the interface you want to fulfil directly (by having actual method definitions), despite the extra ceremony. Inheritance with embedding is a false friend in Go. Stick to composition.
If you want to delegate a call to a "subtype", simply invoke the method on the subtype by writing it out in long form. It doesn't make you feel as intelligent but it's way more robust and won't come back to bite you down the line.
Sometimes you need this though. For instance, if you do a type alias on a UUID, it will fail to marshal to SQL. But if you do a singleton embedded struct, it will work fine. I'm not gonna waste my time writing Scan and Value every time I newtype
A small critique on your examples: you never actually show using the Client interface anywhere. Might be useful to someone new to see how you'd switch out HTTPClient for CachedHTTPClient using it.
There’s probably more but the singular best use-case I’ve found for embedding a struct is when you want a mutex.
That is elegant.
This example looks nice, but I think the naming of the structure and interfaces makes it hard to follow and a cache doesn’t necessarily benefit from it. In fact, it’s worse if your cache isn’t in-memory, and you’re not sharing an instance or a connection pool. If you are, this is just indirection and you can live with `Cache.get` just as well.
44 comments
[ 350 ms ] story [ 1827 ms ] threadThere are lots of drawbacks to doing it that way, the worst of which is that you need to remember to override the method in CachedHTTPClient everytime you add a method to HTTPClient, and the compiler gives you no hints about it.
What happens in case you forget to override the method in CachedHTTPClient?
The super class implementation of the method may perform something that is entirely correct even for the extended class, or it may do something that is inconsistent with the assumptions of the extended class. Either way, there is no way for the compiler or runtime to determine if the omission of the method in the extending class is intentional or a mistake.
This is one reason to prefer Composition over Inheritance, especially in Java.
While there's no particular exact language feature I can point at to say why this happens, in general, Go interfaces are more fluid since they don't have to be declared up front, and end up being kept simpler than Java classes and interfaces, so the concerns about failing to override other methods are greatly, greatly reduced. They are not technically eliminated, but they're pushed way, way down my list of priorities.
[1]: This is not special pleading for Go, it goes well beyond that. A good design in Java is a bad design in Python, a good design in Python is a bad design in Java, etc. If you had two languages where the exact same patterns were appropriate in the exact same way, I'd question whether you actually had two languages.
Bear in mind that when using the interface, the interface is all there is. It essentially erases the other methods from consideration. That's why an interface value in Go is a distinct type; it isn't just "a thing that can happen to hold all these various concrete values", it is a distinct thing with its own method set. So discussion underlying structs and their method sets is a category error. (This is a bit subtle, but important to understand what is actually going on in Go, or any other language with a similar setup.)
I'm not talking hypothetically. I'm talking about what happens in real Go code. Discussing what could happen if people wrote interfaces in a way other than they actually do is what is hypothetical. This is the sort of thing that matters when deciding whether or not a particular pattern is useful in a language. It's rarely entirely down to pure syntax concerns or some sort of Platonic software engineering consideration. In fact, even within the same language you can encounter situations where a pattern makes sense in one framework but is a bad idea in another framework; Javascript is full of such things. (Whether that's for good or bad reasons is a separate consideration; the fact is that it is full of them.)
Well in the example given, there is an interface Client with two methods. If a maintainer controls the Client interface and the HTTPClient implementation, the case can occur where that maintainer updates Client and HttpeClient. Suddenly, CachedHTTPClient in the downstream project has an unchached method and as far as my limited Go knowledge goes, no compiler error.
>I'm talking about what happens in real Go code
The case appears in the blog post. Would you say the blog is not idiomatic Go?
Yes, it's a contrived example to make the point in the blog. Blog samples have to be taken that way. The vast majority of the time real code decorates in Go, it's with either A: an interface of 1 method or B: something sufficiently local in concern that this sort of thing isn't a concern, beyond it just being a bug (a compiler forcing you to specify an override won't save you from just sticking the minimal stub in). Part of why this can be a problem in Java is you tend to get a certain sprawl to your class hierarchy that doesn't occur in Go. Or most other languages, used well. Java's got some unique weaknesses in this area that do not generally translate.
As it is just composition, it is strictly better than inheritance, but still probably unnecessarily confusing versus just writing out the delegation methods yourself.
My mistake. I was mislead by the blog post.
From the article:
> The new type would be interchangeable with the existing client which would minimize the need for changes to existing code.
So with go exported structs it is not fair to say they can be used interchangeably if at any point that instance is used as a parameter, field, or variable that defines the type?
In the examples given in the article, the thing that is used as parameters is the interface Client not the concrete type HTTPClient. Would that not allow CachedHTTPClient to passed around as if it was a Client and would that not show the same issues as inheritance?
Literally all it does is automatically create methods on the outer struct that delegate to the anonymous member.
Unlike inheritance, there is no fragile base class problem and methods on the inner anonymous member can’t dispatch to methods on the outer struct. Also, the “parent” member is just another field in your struct. You can modify it or replace it at runtime, unlike the parent in OOP languages.
For example, imagine you have an io.Reader. It may actually implement io.ReaderTo which is used in some cases to implement alloc-free copying. If you wrap it into a new struct, e.g. ioutil.NopCloser you delegate the main interface but not the bonus interfaces. You can no longer cast to the type of the original reader interface either (concrete type comparison is more important with errors, which had a custom fix in Go just for this)
Sorry if I'm misunderstanding!
In this example, by wrapping an interface in another interface, you lose the ability to cast back down to an interface that the original type fulfilled.
Considering that this is not related to embedding specifically, would you say that decorators in general are bad practice in Go?
If you want to delegate a call to a "subtype", simply invoke the method on the subtype by writing it out in long form. It doesn't make you feel as intelligent but it's way more robust and won't come back to bite you down the line.
Note that this applies to every language with inheritance. Composition is _always_ better.
That is elegant.
This example looks nice, but I think the naming of the structure and interfaces makes it hard to follow and a cache doesn’t necessarily benefit from it. In fact, it’s worse if your cache isn’t in-memory, and you’re not sharing an instance or a connection pool. If you are, this is just indirection and you can live with `Cache.get` just as well.