In golang, the interface
type is a collection of method signatures. Any type that implements the methods defined in the interface is said to “implement/satisfy that interface”. Put another way, interfaces allow you to define a set of methods that can be implemented by different types in different ways. This functionality allows you to define a single interface method that can use the underlying method from a number of different types and their differing implementations.
Empty Interfaces
The simplest interface you can create is an uninitialized empty interface:
var myInterface interface{}
fmt.Println(myInterface)
<nil>
Because the interface is empty, it defines no methods. Because it defines no methods, any type can implement/satisfy it. This is because all types implement at least zero methods.
var myInterface interface{}
myInterface = 3.14
fmt.Println(myInterface)
myInterface = "this is my interface"
fmt.Println(myInterface)
3.14
this is my interface
You can also use custom defined types:
type myType struct {
myName string
}
func main() {
var myInterface interface{}
myInterface = myType{"foo"}
fmt.Println(myInterface)
}
{foo}
Interface Values
Each instance of an interface consists of an underlying concrete value and type. You can use fmt.Printf
to see these values:
type myType struct {
myName string
}
func main() {
var myInterface interface{}
myInterface = 3.14
fmt.Printf("underlying interface value: %v, underlying interface type: %T\n", myInterface, myInterface)
myInterface = "this is my interface"
fmt.Printf("underlying interface value: %v, underlying interface type: %T\n", myInterface, myInterface)
myInterface = myType{"foo"}
fmt.Printf("underlying interface value: %v, underlying interface type: %T\n", myInterface, myInterface)
}
underlying interface value: 3.14, underlying interface type: float64
underlying interface value: this is my interface, underlying interface type: string
underlying interface value: {foo}, underlying interface type: main.myType
The use of an interface grants a single variable (myInterface) the ability to hold values of different underlying types (float64
, string
, and the custom myType
).
Type Assertions
If you wish to perform operations with these underlying interface values, you will not be able to do so directly:
var numberOne interface{} = 1
var numberTwo int = 1
mySum := numberOne + numberTwo
fmt.Println("mySum:", mySum)
invalid operation: numberOne + numberTwo (mismatched types interface {} and int)
You’ll need to use type assertions in order to access the underlying concrete type:
var numberOne interface{} = 1
var numberTwo int = 1
mySum := numberOne.(int) + numberTwo
fmt.Println("mySum:", mySum)
mySum: 2
Interface Methods
Not only can an interface hold different underlying types, an interface method can also run different underlying methods associated with these different underlying types. The classic example is that of a single shape
interface with an area()
method that can run the different area formulas for different underlying shapes.
type shape interface {
area() float64
}
type circle struct {
radius float64
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
type rectangle struct {
length float64
width float64
}
func (r rectangle) area() float64 {
return r.length * r.width
}
type triangle struct {
length1 float64
length2 float64
length3 float64
}
func (t triangle) area() float64 {
s := (t.length1 + t.length2 + t.length3) / 2
return math.Sqrt(s * (s - t.length1) * (s - t.length2) * (s - t.length3))
}
func main() {
c := circle{2}
r := rectangle{1, 1}
t := triangle{3, 4, 5}
// Run the area method directly on each type
fmt.Println("circle area:", c.area())
fmt.Println("rectangle area:", r.area())
fmt.Println("triangle area:", t.area())
// Run the area method on the interface
// The interface method will run the area()
// method for the underlying type
var sh shape
sh = c
fmt.Printf("shape type: %T shape area: %v\n", sh, sh.area())
sh = r
fmt.Printf("shape type: %T shape area: %v\n", sh, sh.area())
sh = t
fmt.Printf("shape type: %T shape area: %v\n", sh, sh.area())
}
circle area: 3.141592653589793
rectangle area: 1
triangle area: 6
shape type: main.circle shape area: 3.141592653589793
shape type: main.rectangle shape area: 1
shape type: main.triangle shape area: 6
Implementation Example
Interfaces are useful when you need to work with different objects that do the same thing differently.
Let’s say we have an application that is written to work with a circle
object.
type circle struct {
radius float64
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) draw() {
// logic for drawing a circle goes here
}
The app has business logic that expects circle
objects so it can draw()
circles and calculate their area()
.
func businessLogicArea(c circle) {
fmt.Println("using circle area for business purposes")
_ = c.area()
}
func businessLogicDraw(c circle) {
fmt.Println("using circle draw for business purposes")
c.draw()
}
When we need to use the business logic, we give it circle
instances.
func main() {
ci := circle{radius: 5.0}
businessLogicArea(ci)
businessLogicDraw(ci)
}
The application now needs to support a new type, the square
. The square
does the same thing the circle does: draw()
squares and calculate their area()
.
type square struct {
length float64
}
func (s square) area() float64 {
return s.length * s.length
}
func (s square) draw() {
// logic for drawing a square goes here
}
In order to add support for this new product, we will need to introduce a shape
interface. This interface defines the methods the circle and square types need in order to be considered a shape
. In this case, it’s the familiar area()
and draw()
.
type shape interface {
area() float64
draw()
}
Now our business logic can use this interface and work with any shape
, not just the circle
.
func businessLogicArea(sh shape) {
fmt.Println("using shape area for business purposes")
fmt.Printf("shape type is %T\n\n", sh)
_ = sh.area()
}
func businessLogicDraw(sh shape) {
fmt.Println("using shape draw for business purposes")
fmt.Printf("shape type is %T\n\n", sh)
sh.draw()
}
func main() {
ci := circle{radius: 5.0}
businessLogicArea(ci)
businessLogicDraw(ci)
sq := square{length: 5.0}
businessLogicArea(sq)
businessLogicDraw(sq)
}
using shape area for business purposes
shape type is main.circle
using shape draw for business purposes
shape type is main.circle
using shape area for business purposes
shape type is main.square
using shape draw for business purposes
shape type is main.square
You can run this code on the Go Playground.
Additional Reading
https://gobyexample.com/interfaces
https://tour.golang.org/methods/9