Thoughts on Whatnot

A blog about .

Composition vs. Inheritance

There’s a situation that, while it doesn’t happen too commonly, annoys me when it does happen. Someone comes to Reddit, or golang-nuts, or somewhere else, and asks about the often repeated refrain about composition vs. inheritance. Sometimes, they get the right answer. Sometimes, someone makes some out-of-nowhere remark about embedding. The problem is that embedding is not in and of itself composition. It can be used in composition, but composition is not a syntactic or behavioral choice in the language; it’s a design pattern.

Composition has little to do with Go inherently. Wikipedia even has a non-Go-specific article about composition and inheritance. However, Go encourages a composition-based approach, both through the utter lack of a typical inheritance system and through implicit interfaces. In this post, I’m going to try to cover what composition looks like in Go. As is standard for attempting to explain Go design patterns, I’m going to use the standard library as an example.

My favorite example of composition is `io.Reader` . It’s a deceptively simple looking interface, containing only a single method: Read([]byte) (int, error). But with that one method, it is also one of the most powerful interfaces in the standard library, as it promotes composition.

Let’s suppose that you were designing a simple program that pulls JSON data, let’s say {"val": "example"}, from some URL. With a typical inheritance-based approach, you might do something like this:

Warning: Rusty Java abilities on display. Read with caution.

public class Data
{
  public String val;

  public static Data get(String url)
  {
    CloseableHttpClient client = new HttpClients.createDefault();
    HttpGet req = new HttpGet(url); // Can this handle HTTPS?
    CloseableHttpResponse rsp = client.execute(req);
    try
    {
      HttpEntity body = rsp.getEntity();
      if (body != null)
      {
        String json = EntityUtils.toString(body);

        Gson gson = new Gson();
        return gson.fromJson(json, Data.class);
      }
    }
    finally
    {
      rsp.close();
    }
  }
}

Well, isn’t that just wonderful looking. I hate Java.

Either way, this is fairly typical. Just look at how many sub-classes are in use here: CloseableHttpClient, because the standard HttpClient isn’t good enough, HttpGet, because there needs to be a separate class for each HTTP method, CloseableHttpResponse, because, again, the standard HttpResponse isn’t good enough… It’s kind of a mess, and it hasn’t even gotten to actually reading the JSON yet.

So, how would I do this in Go?

type Data struct {
  Val string `json:"val"`
}

func GetData(url string) (data Data, err error) {
  rsp, err := http.Get(url)
  if err != nil {
    return data, err
  }
  defer rsp.Body.Close()

  dec := json.NewDecoder(rsp.Body)

  err = dec.Decode(&data)
  return data, err
}

That’s it. Now, you might argue that this is so much shorter due to superior API design, but that’s the point. Favoring composition promotes much cleaner APIs. To really show this off, let’s make one simple change to the above code: Let’s assume that the incoming data is gzipped.

Unfortunately, my Java abilities are far too rusty to be sure about how to do this, but I would guess it would wind up looking something like this:

import some.useful.library.probably.apache.GzippedInputStream;

public class Data
{
  public String val;

  public static Data get(String url)
  {
    CloseableHttpClient client = new HttpClients.createDefault();
    HttpGet req = new HttpGet(url); // Can this handle HTTPS?
    CloseableHttpResponse rsp = client.execute(req);
    try
    {
      HttpEntity body = rsp.getEntity();
      if (body != null)
      {
        String gzip = EntityUtils.toString(body);

        GzippedInputStream unzipper = new GzippedInputStream(gzip);
        StringWriter sw = new StringWriter();
        IOUtils.copy(unzipper, sw, encoding);

        Gson gson = new Gson();
        return gson.fromJson(sw.toString(), Data.class);
      }
    }
    finally
    {
      rsp.close();
    }
  }
}

And in Go?

type Data struct {
  Val string `json:"val"`
}

func GetData(url string) (data Data, err error) {
  rsp, err := http.Get(url)
  if err != nil {
    return data, err
  }
  defer rsp.Body.Close()

  r, err := gzip.NewReader(rsp.Body)
  if err != nil {
    return data, err
  }
  defer r.Close()

  dec := json.NewDecoder(r)

  err = dec.Decode(&data)
  return data, err
}

And again, that’s it. The trick is that json.NewDecoder() doesn’t care what it’s getting so long as it’s an io.Reader. The composition comes into play in `gzip.Reader` : Instead of the hierarchy being gzip.Reader inherits from io.Reader, it’s that gzip.Reader wraps an io.Reader. What this means is that you can read gzipped data from any source the exact same way. Want to read from a TCP connection? Well, `net.Conn` implements io.Reader, so just wrap the network connection with gzip.NewReader(conn). Want to take that gunzipped data and split it on the fly around some delimiter as you read it? Just use `bufio.Scanner` with a custom `SplitFunc` . Thanks to the API being designed around composition, all of these things just work, and, even better, they’re often more efficient than the inheritance-based approach because all of these conversions can be done on the fly, instead of requiring multiple allocations and copies for each one.

And I think that about covers it. There are other examples in the standard library, but this is by far the most prominent one. Another is io.Writer, but it works pretty much the same way as io.Reader, just in reverse. Go encourages this pattern simply by making interface implementation implicit, which, in turn, makes small interfaces practical. In fact, using small interfaces is a Go idiom, and it’s one of the reasons that the io package exists, as it provides standard interfaces that types can provide, allowing for the above.

So get out there and start composing types. And use interfacer. It helps with these kinds of things.


Share

comments powered by Disqus