El polimorfismo permite que una función o un método trabaje con diferentes tipos que implementan una misma interfaz, sin conocer de antemano el tipo específico con el que está trabajando. Esto se traduce en mayor flexibilidad y capacidad de abstracción en el código.
Las interfaces en Go son tipos que definen un conjunto de métodos. Un tipo satisface una interfaz si implementa todos los métodos de esa interfaz. Las interfaces permiten definir comportamientos y trabajar con diferentes tipos de manera uniforme.
Para definir una interfaz en Go, se utiliza la palabra clave type
seguida del nombre de la interfaz y la palabra clave interface
, que contiene la lista de métodos que debe implementar cualquier tipo que satisfaga la interfaz:
type Shape interface {
Area() float64
Perimeter() float64
}
En este ejemplo, Shape
es una interfaz que requiere dos métodos: Area
y Perimeter
.
Cualquier tipo que implemente todos los métodos de una interfaz se considera que satisface esa interfaz. No es necesario declarar explícitamente que un tipo implementa una interfaz, como ocurre en otros lenguajes orientados a objetos.
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
En este ejemplo, tanto Rectangle
como Circle
implementan la interfaz Shape
al proporcionar los métodos Area
y Perimeter
.
El polimorfismo permite que funciones trabajen con cualquier tipo que implemente una interfaz, sin importar el tipo específico. Esto se logra pasando la interfaz como argumento:
func PrintShapeInfo(s Shape) {
fmt.Printf("Area: %f\n", s.Area())
fmt.Printf("Perimeter: %f\n", s.Perimeter())
}
func main() {
r := Rectangle{Width: 3, Height: 4}
c := Circle{Radius: 5}
PrintShapeInfo(r)
PrintShapeInfo(c)
}
En este ejemplo, la función PrintShapeInfo
acepta cualquier tipo que satisfaga la interfaz Shape
, permitiendo trabajar tanto con Rectangle
como con Circle
.
Go favorece la composición sobre la herencia. Esto se puede ver en el uso de interfaces junto con estructuras embebidas (embedding), permitiendo la creación de tipos complejos que satisfacen múltiples interfaces.
type ColoredShape interface {
Shape
Color() string
}
type ColoredRectangle struct {
Rectangle
ShapeColor string
}
func (cr ColoredRectangle) Color() string {
return cr.ShapeColor
}
func main() {
cr := ColoredRectangle{
Rectangle: Rectangle{Width: 3, Height: 4},
ShapeColor: "red",
}
PrintShapeInfo(cr)
fmt.Printf("Color: %s\n", cr.Color())
}
En este ejemplo, ColoredRectangle
satisface la interfaz ColoredShape
, que incluye los métodos de Shape
y un método adicional Color
. ColoredRectangle
compone un Rectangle
y agrega un campo ShapeColor
junto con el método Color
.
Una interfaz vacía (interface{}
) puede contener cualquier tipo, proporcionando una forma de manejar datos de tipo desconocido en tiempo de compilación. Las afirmaciones de tipo (type assertions) y las sentencias switch
de tipo permiten trabajar con estos datos de manera segura.
func PrintAnything(a interface{}) {
fmt.Println(a)
}
func main() {
PrintAnything(123)
PrintAnything("Hello, World!")
PrintAnything(Rectangle{Width: 3, Height: 4})
}
func Describe(a interface{}) {
switch v := a.(type) {
case int:
fmt.Printf("This is an int: %d\n", v)
case string:
fmt.Printf("This is a string: %s\n", v)
case Rectangle:
fmt.Printf("This is a Rectangle with width %f and height %f\n", v.Width, v.Height)
default:
fmt.Println("Unknown type")
}
}
func main() {
Describe(123)
Describe("Hello, World!")
Describe(Rectangle{Width: 3, Height: 4})
}
Para ilustrar el polimorfismo en un caso más realista, consideremos un sistema de pago que soporta múltiples métodos de pago.
type PaymentMethod interface {
Pay(amount float64) string
}
type CreditCard struct {
Owner string
}
func (cc CreditCard) Pay(amount float64) string {
return fmt.Sprintf("%s paid %f using Credit Card", cc.Owner, amount)
}
type PayPal struct {
Email string
}
func (pp PayPal) Pay(amount float64) string {
return fmt.Sprintf("%s paid %f using PayPal", pp.Email, amount)
}
func ProcessPayment(pm PaymentMethod, amount float64) {
fmt.Println(pm.Pay(amount))
}
func main() {
cc := CreditCard{Owner: "Alice"}
pp := PayPal{Email: "alice@example.com"}
ProcessPayment(cc, 100.0)
ProcessPayment(pp, 200.0)
}
En este ejemplo, ProcessPayment
acepta cualquier método de pago que implemente la interfaz PaymentMethod
, permitiendo trabajar con CreditCard
y PayPal
de manera uniforme.
El polimorfismo en Go, facilitado por el uso de interfaces, proporciona una forma poderosa y flexible de trabajar con diferentes tipos. Al definir comportamientos a través de interfaces, Go permite que funciones y métodos operen sobre cualquier tipo que satisfaga una interfaz dada, promoviendo un diseño de código limpio y extensible. Aunque Go no sigue el paradigma orientado a objetos clásico, sus interfaces y enfoque en la composición ofrecen una alternativa robusta para la creación de programas polimórficos.
Jorge García
Fullstack developer