Thursday, June 2, 2011

Interfaces in Go


Interfaces are contracts that exist between an expected implementation of a functionality and its actual implementation. In real life there are innumerable interfaces that we are implicitly bound to and that we expect of others. When we put money in a bank, we expect that the money is there the next day and if they have promised us a daily interest, then that we have the additional amount. How the bank keeps the money safe and how it generates the amount for the interest is not something we usually concern ourselves with. This implicit contract is our interface with the bank, and also therefore, the bank’s interface with us.

In programming, interfaces follow a similar model. We will work out an example that uses Go’s idea of interfaces, which is different from interfaces in programming languages like Java, C++, C#, etc. which have object oriented constructs. Go being procedural implements it differently, but it works just as well and has many positive downstream implications.

Our example takes us through getting the area of various shapes. A rectangle is a shape, a square is a shape, a circle is a shape … and a shape has an area. So Shape, which is an abstract concept, can be an interface. And actual shapes like a circle, rectangle, triangle, etc. can provide its own area. And we can then have a unified way of getting the area of different shapes.

If you are new to programming, the impact of such an idea might not be clear right now, but it is very powerful. If you have traveled to places where you did not know the language, you might have struggled to get information as and when you needed it. "When will the train arrive?" - what if there was one consistent asking that irrespective of where you were in the world. Well, there isn’t, which is why we have to go through the shortened sentences, higher voices, hand waving, pointing at things, writing it out and so on and so forth. In computing too, we have to deal with various types of objects and in order that we may interact with them consistently, interfaces are used.

In the first step, we shall go through just creating a simple rectangle class and print its area.
Full code
package main

import "fmt"

//define a Rectangle struct that has a length and a width
type Rectangle struct { 
   length, width int
}

//write a function Area that can apply to a Rectangle type
func (r Rectangle) Area() int {
   return r.length * r.width
}

func main() {
   r := Rectangle{length:5, width:3} //define a new Rectangle instance with values for its properties
   fmt.Println("Rectangle details are: ",r)  
   fmt.Println("Rectangle's area is: ", r.Area())
}

Rectangle details are: {5 3}
Rectangle's area is: 15


At this point, this code has no interfaces. As we proposed earlier, let us now add interfaces. The Area function is what we will abstract out into the interface called Shaper.

In Go, the convention is to "er" a type to indicate that it is an interface. Shape is best named Shaper when it is an interface. Convert is best named Converter when it is an interface. Not doing so won’t crash your program but it is useful to follow convention as it will be easier for others reading the code to understand what is intended.

Full code
package main

import "fmt"

//Shaper is an interface and has a single function Area that returns an int.
type Shaper interface {
   Area() int
}

type Rectangle struct {
   length, width int
}

//this function Area works on the type Rectangle and has the same function signature defined in the interface Shaper.  Therefore, Rectangle now implements the interface Shaper.
func (r Rectangle) Area() int {
   return r.length * r.width
}

func main() {
   r := Rectangle{length:5, width:3}
   fmt.Println("Rectangle r details are: ", r)  
   fmt.Println("Rectangle r's area is: ", r.Area())  

   s := Shaper(r)
   fmt.Println("Area of the Shape r is: ", s.Area())  
   
}

Rectangle r details are: {5 3}
Rectangle r's area is: 15
Area of the Shape r is: 15

In languages like Java and C#, a type or class that intends to provide implementation for an interface needs to explicitly state that. So in Java, one would have to do:

public class Rectangle implements Shaper { //implementation here }

In Go however, such explicit declaration of implementation is not required. And you can see that in the code. The Shaper interface is defined with a single method Area() int. The Rectangle type has a function with the same signature: func (r Rectangle) Area() int. So Go automatically understands that the Rectangle type implements the Shaper interface. Therefore you can cast a Rectangle type to be a Shaper type s := Shaper(r) and call the Area function on it: s.Area().

So far we have achieved only the same result as before. Let’s add a Square type now and see how interfaces can be used to make the Area call uniformly.

Full code
package main

import "fmt"

type Shaper interface {
   Area() int
}

type Rectangle struct {
   length, width int
}

func (r Rectangle) Area() int {
   return r.length * r.width
}

type Square struct {
   side int
}

//the Square type also has an Area function and therefore, it too, implements the Shaper interface
func (sq Square) Area() int {
   return sq.side * sq.side
}

func main() {
   r := Rectangle{length:5, width:3}
   fmt.Println("Rectangle details are: ",r)  

   var s Shaper
   s = r //equivalent to "s = Shaper(r)" since Go identifies r matches the Shaper interface
   fmt.Println("Area of Shaper(Rectangle): ", s.Area())
   fmt.Println()

   q := Square{side:5}
   fmt.Println("Square details are: ",q)  
   s = q //equivalent to "s = Shaper(q)
   fmt.Println("Area of Shaper(Square): ", s.Area())
   fmt.Println()
}

Rectangle details are: {5 3}
Area of Shaper(Rectangle): 15

Square details are: {5}
Area of Shaper(Square): 25

We now have a Square type also, and in a fashion similar to that of Rectangle, we are able to obtain its area. Let’s get similar output again, but using a loop which will give an indication how interfaces can produce cleaner, simpler, and more scalable code. In the below code we create an array of Shaper instances and then loop through them calling the Area function on each. Same effect as before, but now with improved possibilities.

Full code
package main

import "fmt"

type Shaper interface {
   Area() int
}

type Rectangle struct {
   length, width int
}

func (r Rectangle) Area() int {
   return r.length * r.width
}

type Square struct {
   side int
}

func (sq Square) Area() int {
   return sq.side * sq.side
}

func main() {
   r := Rectangle{length:5, width:3}
   q := Square{side:5}
   shapesArr := [...]Shaper{r, q}

   fmt.Println("Looping through shapes for area ...")
   for n, _ := range shapesArr {
       fmt.Println("Shape details: ", shapesArr[n])
       fmt.Println("Area of this shape is: ", shapesArr[n].Area())
   }
}

Looping through shapes for area ...
Shape details: {5 3}
Area of this shape is: 15
Shape details: {5}
Area of this shape is: 25


12 comments:

  1. Oh man, that last example really helped me grasp the just how awesome interfaces are. Thanks!

    ReplyDelete
  2. Thanks for your poest, this help me how to use interface, but i still not sure when should I use it!!
    Can you show me more example that we need use innterface in my program.
    Let say in my web app, I have two main class that TOPIC (showing the post page) MEMBER (showing member info). I see they are the same as PAGE. They always have id, title with SetTiele, SetId method... so in this case PAGE should be an class or it should be an interface?

    ReplyDelete
  3. The casts to Shaper are mostly not necessary (except for where it is the only type given, e.g. "s := Shaper(r)"). By using the casts, some of the full beauty of Go interfaces is missing.

    i.e. "shapesArr := [...]Shaper{Shaper(r), Shaper(q)}" will work as "shapesArr := [...]Shaper{r, q}", and "s = Shaper(r)" will work as "s = r".

    ReplyDelete
  4. Please take a note of the last comment above and modify your example.

    ReplyDelete
  5. Updated based on those comments. Thanks for the inputs.

    ReplyDelete
  6. Thanks Satish, your post helped me to grasp interfaces. Good job :)

    ReplyDelete
  7. Thanks Satish this was really helpful

    ReplyDelete
  8. I would also suggest you use slices instead of arrays in your tutorials.

    ReplyDelete
  9. very good, thanks

    ReplyDelete
  10. Thanks, your article help me to understand clearly the concept of interfaces.

    ReplyDelete

If you think others also will find these tutorials useful, kindly "+1" it above and mention the link in your own blogs, responses, and entries on the net so that others also may reach here. Thank you.