Home

Behavioural Interfaces

When it comes to designing abstractions interfaces are a fundamental tool in a developer's arsenal. Through my recent ventures into the Go languages I have begun to change my approach to how I use interfaces to a more behavioural approach, for example rather than having an interface to represent a thing such as an animal or a user, I instead aim to represent the behaviours that those things can do; walk, talk, login and so on.

I said this approach has only become a recent change and that is because of that Go offers implicit interfaces which are perfect for abstracting behaviours as it allows the use lots of slim interfaces with little effort on your part.

Lets jump into an example, if we want to represent the behaviours of a human performer. We may say that they can Speak, Perform and Walk. In the old style you might end up creating something like;

type Human interface {
  Speak() string
  Perform()
  Walk(distance int)
}

But by modelling the interfaces based on a thing such as a human we are creating unnecessary duplication and tightly linking our behaviours together. If we wanted to create an Animal performer next we would have to duplicate all of the above under a new Animal interface. That's not very flexible so instead lets extract the behaviours to slim interfaces.

type Speaker interface {
  Speak() string
}

type Performer interface {
  Perform()
}

type Walker interface {
  Walk(distance int)
}

Now we have three behavioural interfaces that have no direct link to a human, they could be satisfied by an animal, a fish or something completely different. This reduces duplication of code and gives us a more flexible layer of abstraction.

Lets take a look at how that behavioural abstraction gives us more reusable code by considering the following function;

type Human struct {}

func (Human) Perform() {
  fmt.Println("What kind of shoes do ninjas wear?")
  fmt.Println("Sneakers!")
}

type Dog struct {}

func (d Dog) Perform() {
  d.Backflip()
}

Then at our point of use we may have

package main

func StartAct(p Performer) {
  p.Perform()
}

func main() {
  acts := []Performer{Dog{}, Human{}}
  for act := range acts {
    StartAct(act)
  }
}

From this we can see that because we focused on the behaviour we have more flexibility in how we can use our implementations. We don't have to create different stage acts for Humans, Dogs or anything else when in the end they are all just trying to perform.

Now at the moment our acts are all just performing so there's little point in the wrapper function we have for it. What if we wanted to add a welcome message for each act? We want to focus on utilising slim interfaces, ideally with one method, so how do we pass through additional information without having to change our performer interface?

This is where type assertions come in beautifully. Take the updated code below (trimmed for brevity)

type ShowNamer interface {
  ShowName() string
}

type Human struct {
  Name string
}

func (Human) Perform() { ... }

func (Human) ShowName() string {
  return h.Name
}

You'll notice we now have a new interface and the human implements it implicitly. So what does that give us the power to do you may be wondering, we are still only passing a Performer to our StageAct. Well thanks to type assertions we can see if our Performer instance implements any other interfaces that might be useful to us.

func StageAct(p Performer) {
  if n, ok := p.(ShowNamer); ok {
    fmt.Printf("Welcome to the fabulous act of %s\n", n.ShowName())
  }
  
  p.Perform()
}

So now we just have to update our main

func main() {
  acts := []Performer{Dog{}, Human{Name: "Bob the Joker"}}
  for act := range acts {
    StartAct(act)
  }
}

And that's it, where our stage act has a show name set our type assertion will kick in and display the name of the performance otherwise we just skip it and let the act get on with their performance.

It's simple and flexibility like this which is a huge draw to the Go langauge for me.

comments powered by Disqus