Day 13: Interfaces and Polymorphism in Go

Venkat Annangi
Venkat Annangi
22/10/2024 19:54 3 min read 53 views
#golang #108 days of golang

Day 13: Interfaces and Polymorphism in Go

Introduction

Interfaces in Go provide a powerful way to define behavior without dictating specific implementations. Unlike traditional object-oriented languages that rely on inheritance, Go uses interfaces to achieve polymorphism and decoupling, allowing types to share common behavior without strict hierarchies.

What Are Interfaces in Go?

An interface in Go is a type that defines a set of methods. If a type provides all the methods declared in an interface, it is said to implement that interface. Interfaces allow you to specify the behavior expected from a type without needing to know its concrete type.

type Speaker interface {
    Speak() string
}
    

Defining and Implementing Interfaces


package main

import "fmt"

// Define the Speaker interface
type Speaker interface {
    Speak() string
}

type Dog struct {
    Name string
}

func (d Dog) Speak() string {
    return "Woof! I am " + d.Name
}

type Cat struct {
    Name string
}

func (c Cat) Speak() string {
    return "Meow! I am " + c.Name
}

func main() {
    dog := Dog{Name: "Buddy"}
    cat := Cat{Name: "Whiskers"}

    var speaker Speaker

    speaker = dog
    fmt.Println(speaker.Speak())

    speaker = cat
    fmt.Println(speaker.Speak())
}
    

Implicit Interface Satisfaction

One of Go's unique features is implicit interface satisfaction. You do not need to explicitly state that a type implements an interface.

Polymorphism with Interfaces


package main

import "fmt"

func announce(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    dog := Dog{Name: "Charlie"}
    cat := Cat{Name: "Lucy"}

    announce(dog)
    announce(cat)
}
    

Type Assertions and Type Switches

Sometimes, you may need to work with the underlying concrete type of a value stored in an interface variable.

Type Assertion Example


package main

func describe(s Speaker) {
    if dog, ok := s.(Dog); ok {
        fmt.Println("This is a dog named", dog.Name)
    } else if cat, ok := s.(Cat); ok {
        fmt.Println("This is a cat named", cat.Name)
    }
}

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Mittens"}

    describe(dog)
    describe(cat)
}
    

Type Switch Example


package main

func describe(s Speaker) {
    switch t := s.(type) {
    case Dog:
        fmt.Println("This is a dog named", t.Name)
    case Cat:
        fmt.Println("This is a cat named", t.Name)
    default:
        fmt.Println("Unknown type")
    }
}
    

Empty Interface and Its Uses

Go’s empty interface (interface{}) is a special type that can hold any value, because all types implement the empty interface.


package main

import "fmt"

func printAnything(i interface{}) {
    fmt.Println(i)
}

func main() {
    printAnything(123)
    printAnything("hello")
    printAnything(true)
}
    

Practical Applications of Interfaces in Go


type Fetcher interface {
    Fetch(url string) (string, error)
}

func GetContent(f Fetcher, url string) string {
    content, err := f.Fetch(url)
    if err != nil {
        return "Error fetching content"
    }
    return content
}

type MockFetcher struct{}

func (m MockFetcher) Fetch(url string) (string, error) {
    return "Mock content", nil
}

func main() {
    mock := MockFetcher{}
    result := GetContent(mock, "http://example.com")
    fmt.Println(result)
}
    

Best Practices with Interfaces

  • Design for Behavior, Not Data
  • Small, Focused Interfaces
  • Don’t Overuse Empty Interfaces
  • Use Interfaces for Testing

Conclusion

Interfaces in Go are a powerful tool for writing flexible, decoupled, and reusable code. By understanding how to define and use interfaces, you can achieve polymorphism, improve code maintainability, and write more modular applications.

Comments