Interface Segregation Principle

Interface types are an interesting feature in the Go programming language. They are meant to encourage creating abstractions by considering the behavior that is common between types, instead of the fields that are common between types.

They are types that define the methods their implementers need to define in order to implement the interface. Types that implement the interface do so implicitly, by defining those methods, rather than explicitly saying they 'implements InterfaceName` or something.

Go naming convention is to suffix “-er” (whenever possible. “-able” is also common) to a type to indicate that it is an interface. So Shaper makes sense for shape interfaces. io.Writer is implemented by concrete types that implement Write.

Interfaces with only one or two methods are common in Go code, such as the famous http.Handler:

type Handler interface {
  ServeHTTP(ResponseWriter, *Request)
}

ResponseWriter is also an interface. It provides access to the methods needed to return the response to the client. Those methods include the standard Write method, so an http.ResponseWriter can be used wherever an io.Writer can be used.

The entire database/sql package is an interface. You refer to it almost exclusively when using a concrete implementation such as lib/pq.

Here’s a longer, common example:

package main

import "fmt"
import "math"

type Shaper interface {
  Area() float64
  Perimeter() float64
}

type Square struct {
  width, height float64
}

func (s Square) Area() float64 {
  return s.width * s.height
}

func (s Square) Perimeter() float64 {
  return 2*s.width + 2*s.height
}

type Circle struct {
  radius float64
}

func (c Circle) Area() float64 {
  return math.Pi * c.radius * c.radius
}

func (c Circle) Perimeter() float64 {
  return 2 * math.Pi * c.radius
}

func Measure(s Shaper) {
  fmt.Println(s)
  fmt.Println(s.Area())
  fmt.Println(s.Perimeter())
}

func main() {
  s := Square{width: 3, height: 4}
  c := Circle{radius: 5}

  Measure(s)
  Measure(c)
}

If we were to delete the Square.Perimeter() function, we’ll get a compilation error at Measure(s) like this:

cannot use s (type Square) as type Shaper in argument to Measure:Square does not implement Shaper (missing Perimeter method)

Using a few ~/.vimrc settings, we get this feedback immediately, when saving the file, which is awesome.

There is more great info about Go’s interfaces in Russ Cox’s “Interfaces” article.

@jferris If I understand Interface Segregation Principle correctly, my Measure() function above would be in violation of ISP if I were to delete the fmt.Println(s.Area()) line, correct? At that point, the Shaper interface used in Measure() has a larger surface area of its interface than just the Perimeter() method that is uses.

That version would still compile in Go, but perhaps it would be more conventional to segregate the Shaper interface into Perimeterable and Areable interfaces. Would that also fix the ISP violation?

1 Like