Skip to content

Interfaces

Basics

  • Interfaces define behavior
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var w Writer = &ConsoleWriter{}
        n, err := w.Write([]byte("Hello Go!"))
        fmt.Println(n, err)
    }
    
    type Writer interface {
        Write([]byte) (int, error)
    }
    
    type ConsoleWriter struct {}
    
    // implementing the interface
    func (w *ConsoleWriter) Write(data []byte) (int, error) {
        n, err := fmt.Println(string(data))
        return n, err
    }
    
  • although interfaces are commonly defined along side structs, they can be created with any type
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        myInt := IntCounter(0)
        var inc Incrementor = &myInt
        for i := 0; i < 3; i++ {
            fmt.Println(inc.Increment())
        }
    
    }
    
    type Incrementor interface {
        Increment() int
    }
    
    type IntCounter int
    
    func (ic *IntCounter) Increment() int {
        *ic++
        return int(*ic)
    }
    

Composing Interfaces

package main

import (
    "fmt"
)

func main() {
    var res WaiterServer = &Restuarant{}
    res.wait("Yemane")
    res.serve("Yemane")
}

type Waiter interface {
    wait(name string) string
}

type Server interface {
    serve(name string) string
}

type WaiterServer interface {
    Waiter
    Server
}

type Restuarant struct{}

func (r *Restuarant) wait(name string) string {
    fmt.Println("May I take your order ", name)
    return name
}

func (r *Restuarant) serve(name string) string {
    fmt.Println("Serving your food! ", name)
    return name
}
package main

import (
    "fmt"
)

func main() {
    // passing only the Waiter part of the Restuarant
    var res Waiter = &Restuarant{}
    res.wait("Yemane")

    // will not be implemented and will result in error
    res.serve("Yemane")
}

Type Conversion

func main() {
    var r WaiterServer = &Restuarant{}
    r.wait("Yemane")
    r.serve("Yemane")

    // type conversion
    rs := r.(WaiterServer)
    fmt.Println(rs)
}

The Empty Interface

  • interface with no methods
  • every type in Go implements the empty interface
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var myObj EmptyInterface = &Bartender{}
        if w, ok := myObj.(Waiter); ok {
            w.wait("Yemane")
        }
    }
    
    type EmptyInterface interface{}
    
    type Waiter interface {
        wait(name string)
    }
    
    type Bartender struct{}
    
    func (b *Bartender) wait(name string) {
        fmt.Println("Would you like a drink? ", name)
    }
    

Type Switches

package main

import (
    "fmt"
)

func main() {
    // empty interface
    var myObj interface{} = 0
    switch myObj.(type) {
    case int:
        fmt.Println("myObj is an int")
    case string:
        fmt.Println("myObj is a string")
    default:
        fmt.Println("myObj is of unknown type")
    }
}

Implementing with Value vs Pointers

  • method set of value is all methods with value receivers
  • method set of pointer is all methods, regardless of receiver type

Best Practices

  • Use many, small interfaces over large monolithic ones
  • Don't export interfaces for types that will be consumed
  • do export interfaces for types that will be used by package
  • Design functions and methods to receive interfaces whenever possible