Golang Interfaces

Mathis Engelbart
applike
Published in
4 min readMar 18, 2019

Written by Mathis Engelbart and Marc Lange

As developers we have learned that keeping a system maintainable is helped by coding with low inherent coupling between separate systems and a high cohesion within the individual systems. Within Go we can use interfaces to keep coupling low between a specified interface and specific implementations. Additionally this increases the cohesion in each system, when it uses interfaces to delegate additional functions. In this post we will look into this use of interfaces in Go.

A Toy Example: Servers and Loggers

Imagine you are implementing a server. This server needs to log some information. High cohesion within the server implementation might motivate you to delegate the logger implementation into another module. A naive first implementation may look like this:

This package can then be used by the server:

The logger package defines some Logger interface and the `DefaultLogger` implements this interface. There are some problems with this implementation:

  1. The New()-function always returns a DefaultLogger, but it’s return value is of type Logger. Thus, changes to the DefaultLogger will not be visible to it’s users without changing the interface.
  2. The server has a strict dependency on package logger. There’s no easy way to switch to a different logger.
  3. The server may eventually need to print some debug information using a different function. In the current design, this would require a change to the Logger-interface and the DefaultLogger-implementation.

Breaking Dependencies

The first problem can easily be resolved by changing the return type of New to be a DefaultLogger. This almost trivial change has substantial impact on the other questions. Because Go does not use an implements-keyword, we now have a logging package with two different purposes represented by two logically separated parts of code. One part defines the Logger interface and one part — the DefaultLogger — happens to implement the interface. The important impact we see: It is not necessary for the interface and the implementation to be part of the same package anymore.

As a consequence we can remove the dependency on the logger package we had in the server package. Since the server only needs the interface but not the implementation of a logger, and since we have now separated these two concerns, we can move the logger-interface to the server package.

This has two effects: We removed a strict dependency from the package server and in addition the abstraction of Log into the interface enables us to choose a new implementation, which could be more verbose, saves its logs somewhere, or just shuts off logs completely.

With this clarity the third problem has almost solved itself, we only need a small change to the logger-interface of the server. Because the logger is injected into the server, we now moved the responsibility to pass an actual implementation of the logger, to the user of a server.

A final optimization can be done inside the logger package. Because there’s only one type left, the naming can be simplified by renaming the DefaultLogger to Logger:

And the server:

This new implementation allows your server to depend only on the functions it actually needs. If you build another server, which only needs the Debug-logger, you can reuse this implementation, but do not have to depend on the Log-function of the implementation. Just let the new server define it’s own Logger interface without a Log-function.

Once you separate the interfaces and interface-consumers from their implementations, you get the additional advantage that this is a neat way to keep dependencies on external code outside of your main program. Imagine we used an external logging library instead of the fmt package above. The logging package works as a wrapper around the external library and breaks the dependency of the main server on this external code. It will be easy to replace the external dependency by implementing another Logger, in another package, with different external dependencies.

Another advantage of this is, that you can easily mock your servers dependencies. Suppose that you want to test the server, for that you can add your own mock, without relying on the external package implementing the interface.

Some exceptions

It can be tedious to always define new interfaces when you need them. Additionally this will probably lead to a lot of the defined interfaces being equal across your code. The overcome these redundant lines of code, you can create a separate package which only takes care of the interfaces and use this wherever needed. Furthermore you can always look for existing interfaces in the standard library. In particular the io package offers many good examples.

Occasionally it will be tricky to define an interface like we did in the Server-Logger-example above. A typical problem can be that the interface methods have references to more complex types. For example, imagine an interface with a method that returns an object that implements some behavior. It can be hard to implement the interface, because the implementation needs to know about the referenced types. If the interface methods even recursively reference the interface itself, this can turn out to be impossible:

Any implementation of Producer needs to know about the type Producer, because it has to return a value of exactly this type. If our server needs a Producer, it will be hard to split implementation and interface the way we did with the logger, because the implementation would have a dependency back on the server.

Conclusion

While there may be a few cases where an implementation needs to know about the interface it implements, it is generally a good idea to keep interfaces separated from implementations. Interfaces allow modules to depend only on small abstractions instead of large implementations, which offers a great way to keep coupling low and cohesion high.

Got until here and would love to work with us on the next big mobile challenges? Check our open positions or drop us a line at jobs@applike.info

--

--