Regístrate para acceder a más de 15 cursos gratuitos de programación con un simulador

Composición en lugar de herencia Python: Profundizando en las clases

Ya tenemos una buena base en programación orientada a objetos (OOP), con conceptos como clases, objetos y métodos. Ahora vamos a profundizar en uno de los aspectos más complejos y a menudo mal entendidos: la herencia.

Hablaremos de cuándo realmente es útil, sus limitaciones y qué otras alternativas existen, todo de manera simple y directa, con ejemplos claros para facilitar la comprensión.


¿Qué es la herencia?

La herencia es una forma de reutilizar código creando clases que "heredan" comportamientos y atributos de otras.

En teoría, suena muy útil. Pero en la práctica, puede volverse un problema si no se usa con cuidado.

Imagina que tenemos esta jerarquía:

class Usuario:
    def __init__(self, nombre):
        self.nombre = nombre

    def saludar(self):
        return f"Hola, soy {self.nombre}"

class Administrador(Usuario):
    def borrar_usuario(self):
        print("Usuario borrado")

Hasta acá todo bien. Pero si empezamos a juntar diferentes características (rol, origen de datos, país, gustos personales), todo comienza a complicarse.


¿Qué limitaciones tiene la herencia?

A primera vista parece lógico usar herencia para organizar entidades que se parecen. Pero hay un gran problema: el mundo real no sigue jerarquías claras.

Imaginemos que trabajamos en un ecommerce y tenemos un concepto de Usuario. ¿Cómo lo clasificamos?

  • Según el género: UsuarioHombre, UsuarioMujer
  • Según el tipo de autenticación: Usuario, Invitado
  • Según su rol: Admin, Miembro
  • Según su cargo: Programador, Tester, Gerente
  • Según la fuente: UsuarioFacebook, UsuarioGitHub
  • Según el país: UsuarioColombia, UsuarioMéxico

Si intentamos modelar todo eso con herencia, terminaríamos con docenas o incluso cientos de clases diferentes:

AdministradorDeFacebookQueViveEnColombiaYEsTester

Como podemos ver, esto se vuelve inmanejable. La herencia obliga a elegir una sola estructura jerárquica, cuando muchas veces necesitamos variar eso según el contexto.

Problemas comunes de la herencia

  • Acoplamiento rígido: las clases quedan demasiado conectadas.
  • Poca flexibilidad: cambiar la estructura es difícil.
  • Confusión entre abstracciones: se mezclan la lógica de negocio con los detalles técnicos.

¿Cuándo NO deberíamos usar herencia?

Un error común es usar herencia para organizar partes del código que no están relacionadas por naturaleza.

Pensemos en esto:

class Usuario:
    pass

class UsuarioSQL(Usuario):
    pass

¿Tiene sentido que el tipo de almacenamiento (SQL) defina una nueva subclase de Usuario? En realidad, el Usuario como entidad no debería “saber” ni depender de cómo se guarda.

Este tipo de herencia mezcla niveles de abstracción: lo conceptual (usuario) con lo técnico (base de datos). Y eso tiende a causar errores difíciles de mantener luego.


¿Qué podemos hacer en su lugar?

Aquí entran dos conceptos fundamentales que nos ayudan a resolver estos problemas: composición y mixins.

1. Composición: Delegar tareas, no heredar

La composición es cuando una clase incluye a otra como parte de su funcionamiento, en lugar de heredarla. Nos da más flexibilidad.

Por ejemplo:

class AlmacenamientoSQL:
    def guardar(self, datos):
        print("Guardando en SQL:", datos)

class Usuario:
    def __init__(self, nombre, almacenamiento):
        self.nombre = nombre
        self.almacenamiento = almacenamiento

    def guardar(self):
        self.almacenamiento.guardar({'nombre': self.nombre})

# Uso
sql = AlmacenamientoSQL()
usuario = Usuario("Juan", sql)
usuario.guardar()

Aquí no heredamos nada del almacenamiento; simplemente lo "inyectamos". De esta forma podemos cambiarlo por otro tipo sin tocar la clase Usuario.

2. Mixins: Reutilización sin herencia compleja

Otra alternativa muy usada es el patrón Mixin. Un mixin es una clase que define funciones específicas, y que otras clases pueden usar a través de herencia múltiple para tener esas capacidades, sin formar una jerarquía concreta.

Ejemplo:

class PuedeImprimir:
    def imprimir(self):
        print("Soy una instancia de", type(self).__name__)

class Usuario:
    def __init__(self, nombre):
        self.nombre = nombre

class UsuarioConImpresion(Usuario, PuedeImprimir):
    pass

u = UsuarioConImpresion("Sara")
u.imprimir()  # Imprime "Soy una instancia de UsuarioConImpresion"

Este patrón evita crear árboles gigantes de herencia, al permitir compartir funcionalidades independientes entre varias clases.


¿Entonces cuándo usar herencia?

La herencia sí tiene su lugar. Es útil cuando:

  • Varias clases comparten código y comportamiento de forma natural.
  • La relación entre clases tiene sentido conceptual (por ejemplo, un Perro sí es un Animal).

Pero si estás mezclando cosas como "de dónde se obtuvo un usuario" o "cómo se almacena", es mejor componer o usar mixins.


Casos reales: Comparación rápida

Escenario Herencia Tradicional Composición Mixin
Usuario que guarda en SQL ❌ Mal diseño ✅ Bien ✅ Útil si es comportamiento reutilizable
Usuario Administrador ✅ Ok si comparte comportamiento base ✅ También posible ✅ Si compartimos funciones como “exportar datos”
Usuario con impresión de info ❌ Heredar de clase base solo para imprimir no tiene sentido ❌ Poco útil ✅ Ideal para mixins reutilizables

Resumen

  • La herencia es poderosa pero también puede hacer nuestro código complejo y poco flexible si la usamos mal.
  • El mundo no siempre sigue jerarquías fijas; evitar modelarlo así ciegamente.
  • Composición y Mixins son alternativas que permiten crear sistemas más modulares y adaptables.
  • Usa herencia cuando haya una verdadera relación conceptual de tipo (“es un”).
  • Evita mezclar abstracciones diferentes (como lógica de negocio y almacenamiento) en una jerarquía de clases.

Para acceder completo a curso necesitas un plan básico

El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.

Obtener acceso
130
cursos
1000
ejercicios
2000+
horas de teoría
3200
test

Obtén acceso

Cursos de programación para principiantes y desarrolladores experimentados. Comienza tu aprendizaje de forma gratuita

  • 130 cursos, 2000+ horas de teoría
  • 1000 ejercicios prácticos en el navegador
  • 360 000 estudiantes
Al enviar el formulario, aceptas el «Política de privacidad» y los términos de la «Oferta», y también aceptas los «Términos y condiciones de uso»

Nuestros graduados trabajan en empresas como:

Bookmate
Health Samurai
Dualboot
ABBYY