Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Struct composition with Go (cheney.net)
108 points by jcxplorer on May 22, 2015 | hide | past | favorite | 16 comments


This is cool, but I find this snippet a tad bizarre:

  client, server := net.Pipe()
        var buf bytes.Buffer
        client = &recordingConn {
                Conn: client,
                Writer: io.MultiWriter(client, &buf),
        }
While it seemingly works, it seems confusing to assign client to an internal field of a new struct, and then overwriting that same client variable with the newly created struct...not to mention passing it into MultiWriter.

Anyway, I like those short posts like this. I've done similar things with embedding and struct "reconstruction" in order to elicit more testable code. It's powerful and useful, indeed.


What is the benefit of re-using the 'client' identifier? There's no closure capturing the identifier.

Compare:

        client, server := net.Pipe()
        var buf bytes.Buffer
        client = &recordingConn {
                Conn: client,
                Writer: io.MultiWriter(client, &buf),
        }
vs

        tempClient, server := net.Pipe()
        var buf bytes.Buffer
        client = &recordingConn {
                Conn: tempClient,
                Writer: io.MultiWriter(tempClient, &buf),
        }
The latter is less confusing to me.


In the end there is no need to embed (exported) io.Writed, it should be replaced with non-exported non-anonymous field.


Since the purpose of this is apparently to provide a fake structure to test against - would another viable approach have been to implement a trivial `Close` function that doesn't do anything?

(context: i am no gopher)

(edit/tangent: i wonder how method calls from inside method calls are resolved when the names are ambiguous?)


You need quite a few more method to be a net.Conn actually: http://golang.org/pkg/net/#Conn. Easier to just overwrite the Write() method like this in that case.


I suspect your tangent is because you don't fully understand how composition works. Basically, it's weaker than you think I believe.

I tried to write an example that demonstrates why I don't think the ambiguity you're referring to is possible.

https://play.golang.org/p/TyMJbOjDdZ


If you just need a close that does not do anything, go has a pretty cool wrapper: `ioutil.NoopCloser`. It takes a reader and returns a reader that can be closed `io.ReadCloser`. Very useful for testing/mocking


Is it reasonable to think of this as multiple inheritance?


Nope, this is composition, not inheritance.


It looks more like inheritance: the methods on net.Conn & io.Writer are automatically exposed. Are you saying that it is composition because you are "inheriting" from interfaces, which isn't traditional inheritance? Or if not, can you explain why you think of this as composition and not inheritance?

Edit: On further thought, this looks to me like composition with automatic delegation (a huge productivity benefit of inheritance). It is unusual because the combined class also implements the interfaces because of Go's interface rules, but I think that the late-binding to implementations is more similar to composition.


The inner structs (of which the outer writer struct is composed) cannot access any of the functionality or fields outside of itself. Each inner struct is completely encapsulated, so you can't override an inner struct method and expect the inner struct's behavior to change.

The accessibility of inner struct methods from the outer struct is a syntactic convenience.

Java: https://gist.github.com/jaekwon/8025b9f3a482b3219a21 Go: https://gist.github.com/jaekwon/0f6e5555ab6a592aa4c8

Once you Go, you never go back. ;)


There are also no virtual methods with struct composition. If B embeds A, "upcasting" an instance of B to A will not allow you to call any methods on B.


This is not inheritance, careful about what you say here since it is totally misleading. while embedding structs is useful in a lot of cases, it might yield unexpected results if you expect embedding to work "more like inheritance".

ex

    type A struct{}
  
    func (a *A)GetSelf()*A{return a}

    type B struct{ A }

    b:=&B{A{}}

b.GetSelf() returns type A , not type B

It gets even more confusing when you start embedding interfaces in structs.


I would say that it's inheritance without polymorphisms or a sort of mixin.


As jerf said - the embedded struct, while exposed to users of the external struct, still has no access to anything in the external struct. You can't override the methods it calls, etc. All the methods it calls will always be its own methods, never one from the external struct.

Inheritance implies is-a, and implies that you can modify some of the internal workings of the class you're inheriting from.

Embedding is strictly has-a. It's really no different than having a member variable in any other language... there's just some syntactic sugar to make it a little nicer to access the member's methods and fields (and the methods "count" for fulfilling interfaces).


It isn't inheritance at all. Read the other replies. When an embedded struct has a method called on it, it is completely oblivious to what it is embedded in, no different than a direct call. It does not get any sort of reference to what would be a the superclass if there was any inheritance going on.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: