Go: interfaces
Let’s have a first look at interfaces in Go
Suppose we have two structs, Manager and Developer, and we want to have a function, showInfo, that prints certain information about them. To achieve this we have to use an interface (or generics, which are available since Go 1.18, but I’ll leave that for another post). Without interfaces the code would look like this:
package main
import "fmt"
type manager struct {
name string
salary int
email string
role string
}
type developer struct {
name string
salary int
email string
role string
manager manager
}
// We are only interested in name, salary and role of our employees
func showInfoM(m manager) {
fmt.Printf("Name: %s, Salary: %d, Role: %s\n", m.name, m.salary, m.role)
}
func showInfoD(d developer) {
fmt.Printf("Name: %s, Salary: %d, Role: %s\n", d.name, d.salary, d.role)
}
func main() {
bob := manager{"Bob", 7000, "bob@example.com", "Manager"}
joe := developer{"Joe", 4000, "joe@example.com", "Developer", bob}
marc := developer{"Marc", 4500, "marc@example.com", "Developer", bob}
showInfoM(bob)
showInfoD(joe)
showInfoD(marc)
}
Output:
Name: Bob, Salary: 7000, Role: Manager
Name: Joe, Salary: 4000, Role: Developer
Name: Marc, Salary: 4500, Role: Developer
The output is fine, but it would be nice if we had one function that works with both types instead of one function for each type. Before we implement our interface, let’s have a look at methods which we will need later on to implement our interface.
func (m *manager) showInfo() {
fmt.Printf("Name: %s, Salary: %d, Role: %s\n", m.name, m.salary, m.role)
}
func (d *developer) showInfo() {
fmt.Printf("Name: %s, Salary: %d, Role: %s\n", d.name, d.salary, d.role)
}
// Inside main function
bob.showInfo()
joe.showInfo()
marc.showInfo()
Output:
Name: Bob, Salary: 7000, Role: Manager
Name: Joe, Salary: 4000, Role: Developer
Name: Marc, Salary: 4500, Role: Developer
You can see how the methods resemble OOP code, just instead of implementing the methods inside classes (which don’t exist in Go) you simply define functions that work on receivers, in this case of type *manager and *developer. But we don’t want to have a function that takes the employee as a receiver, we want one that takes the employee as a parameter. Here is the employee interface:
type employee interface {
showInfo()
}
That’s it! Now every type that implements showInfo is automatically also of type employee which means we can define a function that take type employee as a parameter and pass any type that implements the interface. Since we already defined showInfo on our types manager and developer, they implement the interface, the only thing left to do is to define our showInfo function:
func showInfo(e employee) {
e.showInfo()
}
// Inside main function
showInfo(&bob)
showInfo(&joe)
showInfo(&marc)
Output remains the same:
Name: Bob, Salary: 7000, Role: Manager
Name: Joe, Salary: 4000, Role: Developer
Name: Marc, Salary: 4500, Role: Developer
There you go, we now have one function we (or anyone using our code) call when we want to print employee information instead of one function for each type, a welcoming simplification.