Lab 05: Interfaces & Polymorphism

Objective

Master Go interfaces: implicit satisfaction, the empty interface, type assertions, type switches, and interface composition.

Time

30 minutes

Prerequisites

  • Lab 04 (Structs & Methods)

Tools

  • Docker image: zchencow/innozverse-go:latest


Lab Instructions

Step 1: Defining & Implementing Interfaces

docker run --rm zchencow/innozverse-go:latest go run - << 'EOF'
package main

import (
    "fmt"
    "math"
)

// Interface definition
type Shape interface {
    Area() float64
    Perimeter() float64
    String() string
}

type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }
type Triangle struct{ A, B, C 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) String() string {
    return fmt.Sprintf("Circle(r=%.2f)", c.Radius)
}

func (r Rectangle) Area() float64      { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
func (r Rectangle) String() string {
    return fmt.Sprintf("Rect(%.2fx%.2f)", r.Width, r.Height)
}

func (t Triangle) Area() float64 {
    s := (t.A + t.B + t.C) / 2
    return math.Sqrt(s * (s - t.A) * (s - t.B) * (s - t.C))
}
func (t Triangle) Perimeter() float64 { return t.A + t.B + t.C }
func (t Triangle) String() string {
    return fmt.Sprintf("Triangle(%.0f,%.0f,%.0f)", t.A, t.B, t.C)
}

func printShapeInfo(s Shape) {
    fmt.Printf("%-22s area=%-8.2f perimeter=%.2f\n", s, s.Area(), s.Perimeter())
}

func totalArea(shapes []Shape) float64 {
    total := 0.0
    for _, s := range shapes { total += s.Area() }
    return total
}

func main() {
    shapes := []Shape{
        Circle{5},
        Rectangle{4, 6},
        Triangle{3, 4, 5},
        Circle{1},
        Rectangle{10, 2},
    }

    for _, s := range shapes { printShapeInfo(s) }
    fmt.Printf("\nTotal area: %.2f\n", totalArea(shapes))
}
EOF

💡 Go interfaces are satisfied implicitly — no implements keyword. If a type has all the methods an interface requires, it satisfies the interface automatically. This decouples the type from the interface — Circle doesn't know about Shape. This makes Go's type system incredibly flexible and composable.

📸 Verified Output:


Step 2: Interface Composition

💡 io.Reader, io.Writer, io.Closer are the most important interfaces in Go's standard library. They compose into io.ReadWriter, io.ReadCloser, etc. Because interfaces are implicit, any type that implements Read([]byte) (int, error) is an io.Reader — whether it's a file, a network connection, or a buffer.

📸 Verified Output:


Step 3: Type Assertions & Type Switches

💡 Always use the comma-ok form for type assertions: v, ok := i.(Type). Without ok, a failed assertion panics. Type switches (switch v := i.(type)) are cleaner when handling multiple types. The any type is an alias for interface{} — introduced in Go 1.18.

📸 Verified Output:


Steps 4–8: error interface, Sorter, Payment, Middleware, Capstone

📸 Verified Output:


Summary

Concept
Key insight

Implicit satisfaction

No implements keyword — duck typing

Interface composition

Embed interfaces to build larger ones

Type assertion

v, ok := i.(Type) — comma-ok is safe

Type switch

switch v := i.(type) — handles multiple types

error interface

Error() string — every error implements this

any

Alias for interface{} (Go 1.18+)

Further Reading

Last updated