10 - Unlike a user interface, these take a bit of explanation
Covered in this module:
- interfaces
interface
An interface in Go is a collection of methods. Interface declaration is quite similar to structs except you list the methods that the interface has rather than properties. The interface method parameters and return types can be named. The names are optional but may add clarity to the interface. The following two interfaces are equivalent.
type animal interface {
speak() string
eat(int) string
poo(int) string
}
type animal interface {
speak() (message string)
eat(ounces int) (message string)
poo(ounces int) (message string)
}
Any struct that implements all the methods of an interface is then considered as implementing that interface:
type animal interface {
speak() string
eat(int) string
poo(int) string
}
type dog struct {
name string
digestingFoodOunces int
}
func (d dog) speak() string {
return "rrrr RUFF!"
}
func (d *dog) eat(ounces int) string {
d.digestingFoodOunces += ounces
return fmt.Sprintf("%v ate and is now digesting %v ounces of food", d.name, d.digestingFoodOunces)
}
func (d *dog) poo(ounces int) string {
d.digestingFoodOunces -= ounces
return fmt.Sprintf("%v poo'd and is now digesting %v ounces of food", d.name, d.digestingFoodOunces)
}
Using an interface as a parameter enables a function to accept any struct that implements the interface:
func main() {
d := dog{name: "Ein"}
feedAnimal(&d)
}
func feedAnimal(a animal) {
fmt.Println(a.eat(10))
fmt.Println(a.speak())
}
Because eat()
and poo()
have a pointer receiver, only a pointer dog (the type, not the breed) satisfies the interface. The blackHole
struct below only uses value receivers so it can be passed by value:
type animal interface {
speak() string
eat(int) string
poo(int) string
}
type blackHole struct {
name string
}
func (b blackHole) speak() string {
return "..........."
}
func (b blackHole) eat(ounces int) string {
return fmt.Sprintf("%v ate %v ounces, but it made no difference", b.name, ounces)
}
func (b blackHole) poo(ounces int) string {
return fmt.Sprintf("%v tried to poo %v but nothing can escape", b.name, ounces)
}
func feedAnimal(a animal) {
fmt.Println(a.eat(10))
fmt.Println(a.speak())
}
func main() {
b := blackHole{name: "Gargantua"}
feedAnimal(b)
}
A struct can have extra methods that aren't part of the interface and a struct can implement any number of interfaces.
empty interface
The empty interface type interface{}
can be used to enable a function to take any value. All Go types satisfy the empty interface. fmt.Println
is a variadic function that takes empty interfaces:
fmt.Println(5 * 5)
fmt.Println("some string")
fmt.Println(func() string { return "hi" })
fmt.Println([]string{"tree", "shrub", "bush", "flower", "leaf", "root"})
prints
25
some string
0x10910c0
[tree shrub bush flower leaf root]
Empty interface values must be cast before they can be used as a specific type:
func add(a, b interface{}) interface{} {
aval, aok := (a).(int)
bval, bok := (b).(int)
if aok && bok {
return aval + bval
}
fmt.Printf("%v ok: %v, %v ok: %v\n", a, aok, b, bok)
return nil
}
func main() {
fmt.Println(add(5, 4))
fmt.Println(add("5","4"))
fmt.Println(add(5.5, 4.4))
fmt.Println(add("four", "five"))
}
prints
9
5 ok: false, 4 ok: false
<nil>
5.5 ok: false, 4.4 ok: false
<nil>
four ok: false, five ok: false
<nil>
If you recall from part 2, you can use switch
to check the type of an interface and cast it to that type:
var a interface{}
b := 0
switch value := a.(type) {
case string:
i, _ := strconv.Atoi(value)
b += i
case bool:
b = -b
case int:
b += value
default:
fmt.Println("unexpected type, b is unchanged")
}
Hands on!
- In the repo, open the file
./intermediate/10interfaces.go
- Complete the TODOs
- Run
make 10
from project root (alternatively, typego run ./10interfaces.go
) - Example implementation available on
solutions
branch