Menu
Coddy logo textTech

Por qué Go no tiene herencia

Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 34 de 107.

Los lenguajes orientados a objetos tradicionales como Java y C++ utilizan la herencia para compartir código entre tipos. Una clase hija extiende a una clase padre, heredando todos sus campos y métodos. Go omite deliberadamente esta característica.

La herencia crea un acoplamiento fuerte entre tipos. Cuando una clase padre cambia, todas las clases hijas se ven afectadas.

Las jerarquías de herencia profundas se vuelven difíciles de entender y mantener. El "problema de la clase base frágil" ocurre cuando las modificaciones en una clase base rompen inesperadamente las clases derivadas.

Los diseñadores de Go eligieron un camino diferente: composición sobre herencia. En lugar de decir "un Dog es un Animal", Go te anima a decir "un Dog tiene comportamientos similares a los de un Animal". Este sutil cambio conduce a un código más flexible y fácil de mantener.

Go logra la reutilización de código a través de dos mecanismos que ya has aprendido:

  • Interfaces definen contratos de comportamiento sin detalles de implementación
  • La incrustación de estructuras (Struct embedding) permite que los tipos incluyan otros tipos y reutilicen sus métodos

Considera esta comparación. En la POO tradicional, podrías escribir class Dog extends Animal. En Go, incrustas un struct Animal dentro de Dog e implementas interfaces compartidas. El resultado es una funcionalidad similar con un acoplamiento más débil entre los tipos.

Este capítulo explora la incrustación de structs en profundidad, mostrando cómo Go logra los beneficios de la herencia sin sus inconvenientes.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de notificación que demuestre el enfoque de composición de Go en lugar de la herencia. Crearás tipos que comparten comportamiento a través de interfaces e incrustación de estructuras (struct embedding) en lugar de jerarquías de clases.

Organizarás tu código en tres archivos:

  • notifier.go: Define una interfaz Notifier con un único método Notify(message string) string. También crea una estructura BaseNotifier con un campo Name que será incrustado por otros tipos. Dale a BaseNotifier un método llamado Format(message string) string que devuelva el mensaje prefijado con el nombre del notificador entre corchetes.
  • channels.go: Crea dos tipos de canales de notificación que incrusten BaseNotifier e implementen la interfaz Notifier:
    • EmailNotifier con un campo adicional Address
    • SMSNotifier con un campo adicional Phone
    El método Notify de cada tipo debe usar el método Format incrustado e incluir la información específica de su canal en la salida.
  • main.go: Crea una función llamada SendAlert que acepte cualquier Notifier y un mensaje, y luego devuelva el resultado de llamar a Notify. Lee los detalles de la notificación de la entrada, crea ambos tipos de notificadores y demuestra cómo se pueden usar indistintamente a través de la interfaz.

Se proporcionarán las siguientes entradas:

  • Línea 1: Nombre del notificador para el correo electrónico
  • Línea 2: Dirección de correo electrónico
  • Línea 3: Nombre del notificador para SMS
  • Línea 4: Número de teléfono
  • Línea 5: Mensaje de alerta

Tu método Format en BaseNotifier debe devolver:

[Name] message

Tus métodos Notify deben devolver:

  • EmailNotifier: Email to [Address]: [formatted message]
  • SMSNotifier: SMS to [Phone]: [formatted message]

Por ejemplo, dados Alerts, user@mail.com, Urgent, 555-1234, y Server down, tu salida debería ser:

Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down

Observa cómo ambos tipos de notificador reutilizan el método Format de BaseNotifier a través de la incrustación, mientras que cada uno proporciona su propia implementación de Notify. La función SendAlert funciona con cualquier Notifier sin conocer el tipo concreto; esto es la composición sobre la herencia en acción.

Hoja de referencia

Go utiliza la composición sobre la herencia para compartir código entre tipos, evitando el acoplamiento fuerte y los problemas de la clase base frágil de la herencia tradicional orientada a objetos.

En lugar de jerarquías de clases, Go logra la reutilización de código a través de:

  • Interfaces: definen contratos de comportamiento sin detalles de implementación.
  • Incrustación de estructuras (Struct embedding): permite que los tipos incluyan otros tipos y reutilicen sus métodos.

En lugar de decir "un Perro es un Animal" (herencia), Go fomenta "un Perro tiene comportamientos similares a los de un Animal" (composición).

Ejemplo de incrustación de estructuras

Define una estructura base que será incrustada:

type BaseNotifier struct {
    Name string
}

func (b BaseNotifier) Format(message string) string {
    return "[" + b.Name + "] " + message
}

Incrusta la estructura base en otros tipos para reutilizar sus campos y métodos:

type EmailNotifier struct {
    BaseNotifier  // embedded struct
    Address string
}

func (e EmailNotifier) Notify(message string) string {
    // Can call embedded Format method directly
    return "Email to " + e.Address + ": " + e.Format(message)
}

Define una interfaz que múltiples tipos puedan implementar:

type Notifier interface {
    Notify(message string) string
}

Usa la interfaz para trabajar con diferentes tipos concretos de forma intercambiable:

func SendAlert(n Notifier, message string) string {
    return n.Notify(message)
}

Este enfoque proporciona una funcionalidad similar a la herencia pero con un acoplamiento más débil entre los tipos.

Pruébalo tú mismo

package main

import (
	"bufio"
	"fmt"
	"os"
)

// SendAlert acepta cualquier Notifier y un mensaje, devuelve el resultado de llamar a Notify
// TODO: Implementar la función SendAlert

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Leer el nombre del notificador de correo electrónico
	scanner.Scan()
	emailName := scanner.Text()
	
	// Leer la dirección de correo electrónico
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// Leer el nombre del notificador de SMS
	scanner.Scan()
	smsName := scanner.Text()
	
	// Leer el número de teléfono
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// Leer el mensaje de alerta
	scanner.Scan()
	alertMessage := scanner.Text()
	
	// TODO: Crear un EmailNotifier con emailName y emailAddress
	
	// TODO: Crear un SMSNotifier con smsName y phoneNumber
	
	// TODO: Usar SendAlert para enviar el alertMessage a través de ambos notificadores
	// y mostrar los resultados
	
	// Suprimir las advertencias de variables no utilizadas (elimina esto cuando uses las variables)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
quiz iconPonte a prueba

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