Polimorfismo mediante interfaces
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy. Lección 41 de 107.
El polimorfismo permite tratar distintos tipos de manera uniforme mediante una interfaz compartida. En Go, esto se logra completamente mediante interfaces, sin herencia ni jerarquías de clases.
Cuando una función acepta un tipo de interfaz como parámetro, se puede pasar cualquier tipo concreto que implemente esa interfaz. La función no necesita conocer el tipo específico. Solo le importa el comportamiento definido por la interfaz:
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }
type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
Ahora MakeSound funciona con cualquier tipo que tenga un método Speak():
func main() {
dog := Dog{Name: "Rex"}
cat := Cat{Name: "Whiskers"}
MakeSound(dog) // ¡Guau!
MakeSound(cat) // ¡Miau!
}
La misma llamada a una función produce un comportamiento diferente según el tipo real que se le pase. Esto es el polimorfismo en acción. La función MakeSound se escribe una sola vez, pero funciona con un número ilimitado de tipos, siempre que satisfagan la interfaz Speaker.
Este enfoque mantiene tu código flexible y extensible. Añadir un nuevo tipo que hable no requiere cambios en las funciones existentes; solo tienes que implementar la interfaz y funcionará automáticamente.
Desafío
FácilConstruyamos un sistema de descripción de vehículos que demuestre el polimorfismo en acción. Crearás diferentes tipos de vehículos que compartirán un comportamiento común mediante una interfaz y, después, escribirás una única función que funcione con cualquier vehículo.
Organizarás tu código en dos archivos:
vehicles.go: Define una interfazDescriberque requiera un métodoDescribe() string. Después, crea tres tipos de vehículos que implementen esta interfaz a su manera:Carcon los camposBrandyModel. Su métodoDescribe()devuelveCar: [Brand] [Model]Motorcyclecon los camposBrandyEngineCC(int). Su métodoDescribe()devuelveMotorcycle: [Brand] [EngineCC]ccBicyclecon el campoType(como "Mountain" o "Road"). Su métodoDescribe()devuelveBicycle: [Type]
main.go: Crea una función llamadaPrintDescriptionque acepte cualquierDescribere imprima el resultado de llamar aDescribe(). Lee los detalles de los vehículos desde la entrada, crea uno de cada tipo de vehículo y pasa cada uno aPrintDescriptionpara demostrar que la misma función funciona con los tres tipos diferentes.
Se proporcionarán las siguientes entradas:
- Línea 1: marca del Car
- Línea 2: modelo del Car
- Línea 3: marca del Motorcycle
- Línea 4: cilindrada del motor del Motorcycle (entero)
- Línea 5: tipo de Bicycle
Por ejemplo, dadas las entradas Toyota, Camry, Honda, 600 y Mountain, tu salida debería ser:
Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: MountainObserva que PrintDescription no necesita saber si está recibiendo un Car, Motorcycle o Bicycle. Simplemente llama a Describe() y cada tipo responde con su propia salida única. Esto es polimorfismo: una función y múltiples comportamientos.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// TODO: Crea una función llamada PrintDescription que acepte cualquier Describer
// e imprima el resultado de llamar a Describe()
func main() {
reader := bufio.NewReader(os.Stdin)
// Lee los detalles del coche
carBrand, _ := reader.ReadString('\n')
carBrand = strings.TrimSpace(carBrand)
carModel, _ := reader.ReadString('\n')
carModel = strings.TrimSpace(carModel)
// Lee los detalles de la motocicleta
motoBrand, _ := reader.ReadString('\n')
motoBrand = strings.TrimSpace(motoBrand)
motoEngineStr, _ := reader.ReadString('\n')
motoEngineStr = strings.TrimSpace(motoEngineStr)
motoEngine, _ := strconv.Atoi(motoEngineStr)
// Lee los detalles de la bicicleta
bicycleType, _ := reader.ReadString('\n')
bicycleType = strings.TrimSpace(bicycleType)
// TODO: Crea un Car, Motorcycle y Bicycle usando los valores de entrada
// TODO: Llama a PrintDescription para cada vehículo para demostrar el polimorfismo
fmt.Println("TODO: Print vehicle descriptions")
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker PoolPractica por tu cuenta: Compilador de Go online