Herencia y Composición en Programación Orientada a Objetos

Clase 9 de 30Curso de Go Intermedio: Programación Orientada a Objetos y Concurrencia

Contenido del curso

Programación orientada a objetos

Resumen

Cuando diseñas software orientado a objetos, tarde o temprano aparece la pregunta: ¿debería usar herencia o composición? Este debate es uno de los más intensos en programación, y la forma en que cada lenguaje lo resuelve cambia por completo la arquitectura de tu código. A continuación se exploran ambos enfoques con ejemplos prácticos en TypeScript y Go, revelando ventajas, limitaciones y por qué el polimorfismo queda en el centro de la discusión.

¿Cómo funciona la herencia en TypeScript?

La herencia permite que una clase derivada reutilice propiedades y métodos de una clase base. En TypeScript se utiliza la palabra reservada extends para indicar que una clase hereda de otra [1:00].

  • La clase que comparte sus características se denomina clase base (por ejemplo, BaseEmployee).
  • Las clases que heredan se llaman clases derivadas (como TemporaryEmployee y FullTimeEmployee).
  • Las propiedades declaradas como privadas en la clase base quedan aisladas: ni siquiera las clases hijas acceden directamente a ellas [2:07].
  • Solo los métodos públicos de la clase base están disponibles para las clases derivadas.

Gracias a esto, no necesitas copiar y pegar código en cada clase especializada. Cada clase derivada puede agregar sus propias propiedades —como taxRate en FullTimeEmployee— sin afectar a sus clases hermanas [2:52].

¿Qué papel juega el polimorfismo con la herencia?

El polimorfismo permite tratar distintas clases derivadas como si fueran la clase base [3:08]. Puedes crear una función que reciba un BaseEmployee y llamar al método getMessage() sin importar si le pasas un FullTimeEmployee o un TemporaryEmployee.

Sin embargo, hay una señal de alerta: cuando cada clase hija sobrescribe (override) los mismos métodos de la clase base una y otra vez, tu herencia quizás no esté funcionando como esperabas [3:53]. Algunos lenguajes agregan etiquetas como override para hacer explícito que un método está siendo reemplazado.

La regla fundamental de la herencia es la relación "es un": un TemporaryEmployee es un BaseEmployee, así como un FullTimeEmployee es un BaseEmployee [4:30].

¿Por qué Go prefiere composición sobre herencia?

En Go, la herencia no existe como concepto del lenguaje. En su lugar se aplica un principio muy conocido: composición sobre herencia [4:45].

¿A qué se refiere? Imagina que un FullTimeEmployee deja de necesitar un ID. Si usaras herencia, tendrías que modificar la clase base y eso afectaría a todas las clases derivadas. Con composición, cada struct es independiente y maneja sus propias propiedades [5:09].

En lugar de decir que un empleado temporal es un empleado base, decimos que un empleado temporal tiene propiedades de una persona y propiedades de un empleado. Esa diferencia entre "es" y "tiene" es la esencia de la composición.

¿Cómo se implementa la composición con campos anónimos?

En Go, la composición se logra mediante campos anónimos (anonymous fields) dentro de un struct [6:55]:

go type Person struct { Name string Age int }

type Employee struct { ID int }

type FullTimeEmployee struct { Person Employee }

Al declarar Person y Employee sin asignarles un nombre de campo, accedes directamente a sus propiedades (Name, Age, ID) desde FullTimeEmployee [7:40]. Al imprimir la estructura, Go agrupa visualmente las propiedades según el struct al que pertenecen [8:35].

¿Qué ocurre con el polimorfismo sin herencia?

Aquí aparece el problema clave. Si creas una función getMessage que recibe un tipo Person, no puedes pasarle un FullTimeEmployee directamente [9:22]. Go marca un error porque, bajo composición, FullTimeEmployee no es una persona: simplemente tiene propiedades de persona.

En herencia, el polimorfismo funciona de forma natural porque la relación "es un" lo permite. En composición, esa relación no existe, así que el polimorfismo requiere otro mecanismo: las interfaces [10:15].

Este dilema entre "una clase es" versus "una clase tiene" define cuándo conviene cada enfoque. La composición ofrece mayor flexibilidad y menor acoplamiento, pero sacrifica el polimorfismo directo. Las interfaces en Go resuelven exactamente ese vacío, permitiendo definir comportamientos compartidos sin depender de jerarquías rígidas.

¿Prefieres herencia o composición en tus proyectos? Comparte tu experiencia y cuéntanos qué consideraciones aplicas antes de elegir una u otra.