Volver a la página principal
martes 16 julio 2024
11

Polimorfismo en Go

¿Qué es el Polimorfismo?

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.

Interfaces en Go

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.

Definición de Interfaces

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.

Implementación de Interfaces

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.

Uso del Polimorfismo

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.

Polimorfismo y Composición

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.

Ejemplo de Composición

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.

Interfaces Vacías y Type Assertions

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.

Interfaz Vacía

func PrintAnything(a interface{}) {
    fmt.Println(a)
}

func main() {
    PrintAnything(123)
    PrintAnything("Hello, World!")
    PrintAnything(Rectangle{Width: 3, Height: 4})
}

Type Assertions

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})
}

Ejemplo Completo: Sistema de Pago

Para ilustrar el polimorfismo en un caso más realista, consideremos un sistema de pago que soporta múltiples métodos de pago.

Definición de la Interfaz

type PaymentMethod interface {
    Pay(amount float64) string
}

Implementación de Métodos de Pago

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)
}

Uso del Polimorfismo

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.

Conclusión

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.

Etiquetas:
go
Compartir:
Creado por:
Author photo

Jorge García

Fullstack developer