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.