Introduction

  • Interfaces are a collection of method signatures.
type shape interface {
    area() int
    perimeter() int
}

In the above example, shape is anything that can be used to calculate its area and parameter. If you check the example below, you can see that rectangle implements both the area() and perimeter() methods. So, it means rectangle implements the shape interface.

type rectangle struct {
    length int
    width  int
}

func (r rectangle) area() int {
    return r.length * r.width
}

func (r rectangle) perimeter() int {
    return (r.length + r.width) * 2
}
  • Multiple types can implement the same interface. If there is another struct named circle with area() and perimeter() methods, then it also implements the shape interface.
  • Likewise, a single type can implement multiple interfaces as well. For example, an empty interface like interface {} is always implemented by every type.
  • In Go, interfaces are implemented implicitly. If you check the above function, you can see that we have never explicitly mentioned that the rectangle struct is implemented by shape (like we do in Java).

Named argument interfaces

You can also use named arguments and return data in interface methods. If you check the below function, it is not that clear what are the arguments passing to the Copy() method. You only know that they should be strings.

type Copier interface {
    Copy(string, string) int
}

Therefore, to give more readable interface methods, you can define the interface like below.

type Copier interface {
    Copy(sourceFile string, destFile string) (copiedBytes int)
}

Type assertion

Assume you have several types implemented by the shape interface. But you need to write a function to take the area of a rectangle. In such a case, you should use type assertion.

func getArea(s shape) int {
    r, ok := s.(rectangle)
    if ok {
        return r.length * r.width
    }
    return 0
}

Here, you pass a shape and check whether it's a rectangle. If it is, the value of ok would be true. And r would get a rectangle struct.

Type Switch

Instead of type assertion, you can also use switch cases.

func printNumericValue(num interface{}) {
    switch v := num.(type) {
    case int:
        fmt.Printf("%d", v)
    case string:
        fmt.Printf("%s", v)
    default:
        fmt.Printf("%T", v)
    }
}

Clean Interfaces

  • Keep interfaces as simple as you can.
  • Don't make the interface aware of the underlying types (ex: shape interface should not have methods like isCircle()).

Interfaces are not classes. They are much simpler. They don't have constructors or deconstructors to create/destroy data. There is no hierarchy of passing behavior (parent-to-child behavior).