Make Go Interfaces work for you

Part 1: Understanding the concept of an Interface.

Cheikh seck
5 min readAug 6, 2023
https://unsplash.com/photos/vZ8FexauR5o

When I first began to write code with Go I found it difficult to wrap my head around interfaces and understand its importance. Nowadays, I can’t see myself writing a program without defining an interface type. Here are some of the reasons I use interfaces:

  • Mocking: Being able to pass an implementation of an interface for testing purposes.
  • Scaffolding: Write code for functionality I have yet to implement but understand which variables or functionality I need.
  • Volatility: To have the capability of switching my database by adding a new implementation of an interface and keeping the code that relies on this “database” interface.
  • Interoperability: To make the different aspects of my code base compatible by having it communicate with each other through interfaces. This characteristic can be better expressed if you’re developing a library and plan on using it with two or more projects.

Leverage your real-word experiences to understand code

Figure 1

Figure 1 depicts, more or less, the universal (or planetary) symbol of the on and off switch. There are numerous devices that make use of this button and it makes operating electronics much easier because we’re familiar with this interface. There are two arguments one can make about the commonality of this button and the second one is far-fetched:

  • 1. It’s a symbol that’s understood by everyone and therefore reduces the learning curve to operate the equipment.
  • 2. There’s a global conspiracy where pressing the button increases the strength of the secret lizard people kingdom that dwells beneath the Earth’s crust because the symbol represents a God they worship.

Following Occam’s razor and simple rationality, the first answer is obviously correct. The power button is a good example of how a simple interface make’s things easier to comprehend, and code is no different. A washing machine and laptop have different underlying components but may share the same on/off symbol. In this scenario, the legendary on/off button can be perceived as an interface that changes the power state of electronic equipment.

When it comes to code, the same argument can be made in the sense that two structs implementing the same interface may have different underlying behavior.

What is a Go interface?

“Interfaces are named collections of method signatures.” An interface can be passed via function parameter or set as the value of a struct field; in my opinion, an interface is a variable with methods and zero fields.

Listing 1

type Appliance interface {
On() error
Off() error
}

Listing 1 depicts the interface of an appliance; this interface has two functions: one to turn on the appliance and another to power it down. While defining an interface with Go, it’s also a good idea to keep interfaces small as it’s easier to retain the methods offered by it.

Listing 2

type Laptop struct {
Power bool
}

func (l Laptop) On() error {
if !l.Power {
return errors.New("Laptop needs power")
}
return nil
}

func (l Laptop) Off() error {
return nil
}

Listing 2 depicts an implementation of the appliance interface — A laptop is not necessarily an appliance, but you get the point. In this implementation, the struct will check if the field variable Power is set to true and returns an error if not — with Power representing if the laptop has a charge.

Listing 3


type WashingMachine struct {
HasClothes bool
}

func (w WashingMachine) On() error {
if !w.HasClothes {
return errors.New("Washing machine needs clothes loaded first")
}
return nil
}

func (w WashingMachine) Off() error {
return nil
}

Listing 3 showcases another implementation of the appliance interface; this instance represents a washing machine. Compared to a laptop, the washing machine will check for different things prior to performing the operation it was designed for. I chose this example to illustrate how an interface gives developers the freedom of choosing the checks their user defined struct performs and still communicate in a predictable manner with other aspects of their code base.

Testing out the code

Listing 4

func main() {
var a Appliance

a = Laptop{}
fmt.Println("Turning laptop on:", a.On())

a = WashingMachine{}
fmt.Println("Turning washing machine on:", a.On())
}

Listing 4 depicts an example of trying to turn on both the laptop and washing machine. Notice how I’m reusing the same variable a; this is possible because both the Laptop and WashingMachine structs implement and satisfy the Appliance interface.

Listing 5

Turning laptop on: Laptop needs power
Turning washing machine on: Washing machine needs clothes loaded first

Program exited.

Listing 5 depicts the output of the program defined in listing 4; for each appliance type, I’m calling the On method and I’m met with a different error message that’s originating from the implementation of each Appliance.

I chose the interface name Appliance because if I were to draw a Venn diagram of a Laptop and a Washing machine the intersection would be that each piece of equipment has a power on switch; this same notion should be applied when designing an interface by having its methods operate in the same domain.

Conclusion

The examples given in this post are surface level but a great step towards understanding how to think about interfaces. I can’t imagine building a project without an interface because it makes development and testing much easier. A lot of times people say customer’s don’t care about an implementation, just ship it — they will care if there’s a vulnerability within your code and it’s taking your team week’s to patch it because your code base is brittle and difficult to update.

On the same subject of completing projects — shipping is important too, so as a developer it’s important to find a good balance between writing code that’s volatile and not over engineered. The truth hurts, sometimes, and customers won’t care if you waste weeks writing a sophisticated alert package when you can use Grafana.

In the next post, I will move towards more actionable code by writing a web service that will make heavy use of interfaces.

You can play with the code outlined in this post here: https://go.dev/play/p/O8A0r68_2OW

--

--