Polymorphism is a core concept in object-oriented programming (OOP) that allows objects of different types to be treated as objects of a common interface. In Go, interfaces provide a way to achieve polymorphism, enabling more flexible and scalable code.
Basic Example of Polymorphism in Go
At the basic level, you can use interfaces to define common methods between different types. Consider the example of defining a common 'speak' method:
package main
import "fmt"
// Declare an interface with a single method
interface Speaker {
Speak() string
}
// Implement the Speaker interface for different types
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct{}
func (c Cat) Speak() string {
return "Meow!"
}
func main() {
var speaker Speaker
// Assign a Dog to the speaker
speaker = Dog{}
fmt.Println(speaker.Speak())
// Assign a Cat to the speaker
speaker = Cat{}
fmt.Println(speaker.Speak())
}
In this basic example, we define an interface Speaker and implement it for two types: Dog and Cat. The main function demonstrates polymorphism by interacting with different types through the same interface.
Intermediate Example: Polymorphism with Multiple Methods
Let's extend this example to allow more complex behavior with interfaces using multiple methods:
package main
import "fmt"
// Define an interface with multiple methods
interface Animal {
Speak() string
Name() string
}
type Tiger struct{}
func (t Tiger) Speak() string {
return "Roar!"
}
func (t Tiger) Name() string {
return "Tiger"
}
type Elephant struct{}
func (e Elephant) Speak() string {
return "Trumpet!"
}
func (e Elephant) Name() string {
return "Elephant"
}
func animalDetails(a Animal) {
fmt.Printf("%s says: %s\n", a.Name(), a.Speak())
}
func main() {
var animal Animal
// Use Tiger and Elephant types
t := Tiger{}
e := Elephant{}
animalDetails(t)
animalDetails(e)
}
In this example, we can provide more detailed interaction with Animal by introducing additional methods, resulting in better flexibility and decoupled code components.
Advanced Example: Dynamic Type Assertion
Dynamic type assertion in Go provides the ability to work with and differentiate between interface implementations at runtime:
package main
import "fmt"
interface Furniture {
Material() string
}
type Chair struct{}
func (c Chair) Material() string {
return "Wood"
}
type Table struct{}
func (t Table) Material() string {
return "Metal"
}
func checkType(f Furniture) {
switch v := f.(type) {
case Chair:
fmt.Printf("It's a Chair made of %s\n", v.Material())
case Table:
fmt.Printf("It's a Table made of %s\n", v.Material())
default:
fmt.Println("Unknown furniture type")
}
}
func main() {
var furniture Furniture
chair := Chair{}
table := Table{}
checkType(chair)
checkType(table)
}
In this advanced example, we demonstrate type assertion using a switch statement, allowing us to differentiate between specific types implementing the Furniture interface at runtime.
By understanding and utilizing interfaces in Go, developers can write code that is more modular, testable, and robust against changes. This is a powerful aspect of building applications in Go that adhere to solid design principles.