Go Interface
An interface in Go is a type defined using a set of method signatures. The interface defines the behavior for similar type of objects.
For example, Here is an interface that defines the behavior for Geometrical shapes:
// Go Interface - `Shape`
type Shape interface {
Area() float64
Perimeter() float64
}
An interface is declared using the type keyword, followed by the name of the interface and the keyword interface
. Then, we specify a set of method signatures inside curly braces.
Implementing an interface in Go
To implement an interface, you just need to implement all the methods declared in the interface.
Go Interfaces are implemented implicitly
Unlike other languages like Java, you don’t need to explicitly specify that a type implements an interface using something like an implements
keyword. You just implement all the methods declared in the interface and you’re done.
Here are two Struct types that implement the Shape
interface:
// Struct type `Rectangle` - implements the `Shape` interface by implementing all its methods.
type Rectangle struct {
Length, Width float64
}
func (r Rectangle) Area() float64 {
return r.Length * r.Width
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Length + r.Width)
}
// Struct type `Circle` - implements the `Shape` interface by implementing all its methods.
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 (c Circle) Diameter() float64 {
return 2 * c.Radius
}
Using an interface type with concrete values
An interface in itself is not that useful unless we use it with a concrete type that implements all its methods.
Let’s see how an interface can be used with concrete values.
An interface type can hold any value that implements all its methods
package main import ( "fmt" ) func main() { var s Shape = Circle{5.0} fmt.Printf("Shape Type = %T, Shape Value = %v\n", s, s) fmt.Printf("Area = %f, Perimeter = %f\n\n", s.Area(), s.Perimeter()) s = Rectangle{4.0, 6.0} fmt.Printf("Shape Type = %T, Shape Value = %v\n", s, s) fmt.Printf("Area = %f, Perimeter = %f\n", s.Area(), s.Perimeter()) }
# Output Shape Type = main.Circle, Shape Value = {5} Area = 78.539816, Perimeter = 31.415927 Shape Type = main.Rectangle, Shape Value = {4 6} Area = 24.000000, Perimeter = 20.000000
Using Interface types as arguments to functions
package main import ( "fmt" ) // Generic function to calculate the total area of multiple shapes of different types func CalculateTotalArea(shapes ...Shape) float64 { totalArea := 0.0 for _, s := range shapes { totalArea += s.Area() } return totalArea } func main() { totalArea := CalculateTotalArea(Circle{2}, Rectangle{4, 5}, Circle{10}) fmt.Println("Total area = ", totalArea) }
# Output Total area = 346.7256359733385
Using Interface types as fields
package main import ( "fmt" ) // Interface types can also be used as fields type MyDrawing struct { shapes []Shape bgColor string fgColor string } func (drawing MyDrawing) Area() float64 { totalArea := 0.0 for _, s := range drawing.shapes { totalArea += s.Area() } return totalArea } func main() { drawing := MyDrawing{ shapes: []Shape{ Circle{2}, Rectangle{3, 5}, Rectangle{4, 7}, }, bgColor: "red", fgColor: "white", } fmt.Println("Drawing", drawing) fmt.Println("Drawing Area = ", drawing.Area()) }
# Output Drawing {[{2} {3 5} {4 7}] red white} Drawing Area = 55.56637061435917
Interface values: How does an interface type work with concrete values?
Under the hood, an interface value can be thought of as a tuple consisting of a value and a concrete type:
// interface
(value, type)
Let’s see an example to understand more:
package main
import (
"fmt"
)
func main() {
var s Shape
s = Circle{5}
fmt.Printf("(%v, %T)\n", s, s)
fmt.Printf("Shape area = %v\n", s.Area())
s = Rectangle{4, 7}
fmt.Printf("(%v, %T)\n", s, s)
fmt.Printf("Shape area = %v\n", s.Area())
}
# Output
({5}, main.Circle)
Shape area = 78.53981633974483
({4 7}, main.Rectangle)
Shape area = 28
Checkout the output of the above program and notice how the variable s
has information about the value as well as the type of the Shape
that is assigned to it.
When we call a method on an interface value, a method of the same name on its underlying type is executed.
For example, in the above program, when we call the method Area()
on the variable s
, it executes the Area()
method of its underlying type.