Abstracción en programación orientada a objetos: qué es y ejemplos

Alex Jimenez
Alex Jimenez
Actualizado el Sep 20, 2026


Abstracción en programación orientada a objetos: qué es y ejemplos

La abstracción en programación orientada a objetos es el principio que nos permite representar únicamente las características y comportamientos importantes de un objeto, dejando fuera los detalles que no necesitamos conocer para utilizarlo.

Dicho de otra forma, la abstracción nos permite concentrarnos en qué hace un objeto en lugar de preocuparnos constantemente por todos los detalles de cómo lo hace. Es uno de los conceptos fundamentales de la programación orientada a objetos (POO) y resulta especialmente útil cuando desarrollamos sistemas grandes.

Un ejemplo cotidiano puede ayudarnos a entenderlo. Para conducir un automóvil utilizamos el volante, los pedales y la palanca de cambios, pero no necesitamos conocer cada proceso mecánico que ocurre dentro del motor.

En programación podemos aplicar una idea similar: ofrecer una interfaz sencilla mientras ocultamos o dejamos en segundo plano la complejidad necesaria para realizar una operación.

¿Qué es la abstracción en programación orientada a objetos?

La abstracción en POO consiste en identificar las características esenciales de una entidad y representarlas mediante una estructura que permita trabajar con ella sin depender de todos sus detalles internos.

Imagina que estamos desarrollando un sistema para administrar diferentes tipos de vehículos.

Podríamos identificar características comunes como:

  • marca;
  • modelo;
  • velocidad;
  • capacidad de arrancar;
  • capacidad de detenerse.

Un automóvil y una motocicleta implementan internamente estas acciones de maneras que pueden ser diferentes.

Sin embargo, desde el punto de vista de nuestro programa podemos establecer una abstracción común:

Vehiculo
 ├── arrancar()
 ├── acelerar()
 └── detener()

La clase Vehiculo representa el concepto general.

Después podemos crear implementaciones concretas:

Vehiculo
 ├── Automovil
 ├── Motocicleta
 └── Camion

Nuestro código puede trabajar con la idea de un vehículo sin necesitar conocer desde el principio cada detalle de todos los vehículos posibles.

Ese es el poder de la abstracción.

Definición sencilla de abstracción en POO

Si necesitas una definición corta, puedes recordar esta:

La abstracción en programación orientada a objetos consiste en representar las características esenciales de un objeto y ofrecer una forma sencilla de utilizarlo, evitando que quien lo usa tenga que conocer todos sus detalles de implementación.

La palabra importante aquí es esencial.

Abstraer no significa eliminar información al azar.

Significa decidir qué información necesita conocer una parte del sistema y qué detalles pueden permanecer detrás de una interfaz más sencilla.

Polimorfismo Flexibilidad Y Reutilizacion De Codigo En La Poo

Un ejemplo de abstracción de la vida real

Pensemos en una cafetera.

Para preparar café normalmente realizamos algo parecido a esto:

  1. agregamos agua;
  2. colocamos café;
  3. presionamos un botón;
  4. esperamos.

Como usuarios no necesitamos controlar directamente la resistencia, la temperatura exacta del agua, la presión interna o cada componente eléctrico.

La cafetera nos proporciona una interfaz sencilla.

Podríamos representarla así:

class Cafetera:
    def preparar_cafe(self):
        print("Preparando café...")

El usuario solamente necesita hacer:

cafetera = Cafetera()
cafetera.preparar_cafe()

Pero internamente la operación podría ser mucho más compleja:

class Cafetera:
    def preparar_cafe(self):
        self._calentar_agua()
        self._bombear_agua()
        self._extraer_cafe()
        self._servir()

    def _calentar_agua(self):
        print("Calentando agua")

    def _bombear_agua(self):
        print("Bombeando agua")

    def _extraer_cafe(self):
        print("Extrayendo café")

    def _servir(self):
        print("Sirviendo café")

Para preparar café seguimos utilizando:

cafetera.preparar_cafe()

La complejidad interna aumentó, pero la forma de utilizar el objeto no cambió.

Ese ejemplo muestra una de las ideas centrales de la abstracción: podemos interactuar con una funcionalidad mediante una interfaz simple sin necesitar controlar cada paso interno.

¿Para qué sirve la abstracción?

La abstracción ayuda principalmente a manejar la complejidad.

En una aplicación pequeña puede parecer innecesaria.

Pero imagina un proyecto formado por cientos de clases, servicios externos, bases de datos y miles de funciones.

Si cada parte del programa tuviera que conocer exactamente cómo funcionan todas las demás, realizar cualquier cambio sería complicado.

La abstracción permite establecer límites.

Una parte del sistema puede decir:

“Necesito guardar este usuario”.

Sin preocuparse necesariamente por si los datos se almacenarán en PostgreSQL, MySQL, un archivo o un servicio remoto.

Esa decisión puede quedar detrás de una abstracción.

¿Por qué es importante la abstracción en POO?

La abstracción en programación orientada a objetos aporta varios beneficios importantes.

Reduce la complejidad

Permite trabajar con conceptos de alto nivel sin pensar constantemente en todos sus detalles internos.

Facilita el mantenimiento

Una implementación puede cambiar sin obligar necesariamente a modificar el código que utiliza su interfaz.

Mejora la organización

Las responsabilidades pueden separarse mediante clases y contratos claramente definidos.

Favorece la reutilización

Diferentes clases pueden compartir una abstracción común.

Facilita las pruebas

Podemos sustituir determinadas implementaciones por versiones controladas durante las pruebas.

Reduce el acoplamiento

El código puede depender de una abstracción en lugar de estar conectado directamente a una implementación específica.

Este último punto es especialmente importante cuando comenzamos a desarrollar aplicaciones grandes.

¿Cómo funciona la abstracción en programación?

Podemos pensar en la abstracción como una separación entre dos preguntas:

¿Qué necesito hacer?

y:

¿Cómo se hace internamente?

Supongamos que tenemos un sistema de pagos.

Nuestra aplicación necesita realizar:

procesar_pago()

Pero existen diferentes proveedores:

PayPal
Tarjeta
Transferencia

Cada proveedor puede tener reglas y procesos diferentes.

Sin abstracción podríamos llenar nuestro programa de condiciones:

if metodo == "paypal":
    # lógica de PayPal
elif metodo == "tarjeta":
    # lógica de tarjeta
elif metodo == "transferencia":
    # lógica de transferencia

Esto puede funcionar inicialmente, pero comienza a complicarse cuando aparecen nuevos métodos.

Podemos definir una abstracción:

MetodoPago
    procesar_pago()

Y después crear diferentes implementaciones:

MetodoPago
 ├── PagoPayPal
 ├── PagoTarjeta
 └── PagoTransferencia

Ahora todas las implementaciones comparten una idea común: procesar un pago.

Abstracción en Python

Python proporciona herramientas específicas para trabajar con clases abstractas mediante el módulo abc.

Las dos piezas que veremos con mayor frecuencia son:

ABC

y:

abstractmethod

Podemos importarlas así:

from abc import ABC, abstractmethod

Después podemos crear una clase abstracta.

from abc import ABC, abstractmethod

class Vehiculo(ABC):

    @abstractmethod
    def arrancar(self):
        pass

Aquí Vehiculo representa una abstracción.

Estamos indicando que cualquier clase concreta que cumpla este contrato deberá proporcionar una implementación para:

arrancar()

¿Qué es una clase abstracta?

Una clase abstracta es una clase diseñada principalmente para definir una estructura o comportamiento común que otras clases deberán completar.

Normalmente no queremos crear objetos directamente de ella.

Por ejemplo:

from abc import ABC, abstractmethod

class Animal(ABC):

    @abstractmethod
    def hacer_sonido(self):
        pass

Animal representa una idea general.

Sabemos que los animales de nuestro modelo deben poder realizar una acción llamada:

hacer_sonido()

Pero la clase general no decide cuál será ese sonido.

Esa responsabilidad corresponde a las clases concretas.

¿Qué es un método abstracto?

Un método abstracto declara una operación que las clases derivadas deben implementar.

En Python podemos definirlo utilizando:

@abstractmethod

Por ejemplo:

from abc import ABC, abstractmethod

class Animal(ABC):

    @abstractmethod
    def hacer_sonido(self):
        pass

Después creamos:

class Perro(Animal):

    def hacer_sonido(self):
        return "Guau"

Y otra implementación:

class Gato(Animal):

    def hacer_sonido(self):
        return "Miau"

Ahora podemos utilizarlas:

perro = Perro()
gato = Gato()

print(perro.hacer_sonido())
print(gato.hacer_sonido())

Resultado:

Guau
Miau

Ambas clases respetan la misma abstracción, pero implementan el comportamiento de manera diferente.

Ejemplo completo de abstracción en Python

Veamos un ejemplo un poco más realista.

Queremos desarrollar un sistema que calcule el área de diferentes figuras geométricas.

Podemos crear una clase abstracta:

from abc import ABC, abstractmethod

class Figura(ABC):

    @abstractmethod
    def calcular_area(self):
        pass

Ahora implementamos un círculo:

import math

class Circulo(Figura):

    def __init__(self, radio):
        self.radio = radio

    def calcular_area(self):
        return math.pi * self.radio ** 2

Después un rectángulo:

class Rectangulo(Figura):

    def __init__(self, ancho, alto):
        self.ancho = ancho
        self.alto = alto

    def calcular_area(self):
        return self.ancho * self.alto

Podemos utilizar ambas clases:

figuras = [
    Circulo(5),
    Rectangulo(4, 6)
]

for figura in figuras:
    print(figura.calcular_area())

Nuestro bucle no necesita preguntar:

if isinstance(figura, Circulo):

ni:

if isinstance(figura, Rectangulo):

Simplemente sabe que cada objeto ofrece:

calcular_area()

La abstracción nos proporciona un contrato común.

Además, este ejemplo se combina naturalmente con otro concepto de POO: el polimorfismo.

¿Qué ocurre si no implementamos un método abstracto?

Esta es una ventaja práctica de utilizar ABC.

Imaginemos que creamos:

class Triangulo(Figura):
    pass

Y después intentamos:

triangulo = Triangulo()

Python generará un error porque Triangulo no implementó el método abstracto:

calcular_area()

La clase base está obligando a sus implementaciones concretas a respetar un contrato mínimo.

Podemos solucionarlo así:

class Triangulo(Figura):

    def __init__(self, base, altura):
        self.base = base
        self.altura = altura

    def calcular_area(self):
        return (self.base * self.altura) / 2

Ahora sí podemos crear:

triangulo = Triangulo(10, 5)

print(triangulo.calcular_area())

¿Se puede instanciar una clase abstracta en Python?

Si una clase contiene métodos abstractos pendientes, no podemos crear una instancia directamente.

Con esta clase:

from abc import ABC, abstractmethod

class Figura(ABC):

    @abstractmethod
    def calcular_area(self):
        pass

esto no funcionará:

figura = Figura()

¿Y por qué querríamos evitarlo?

Porque Figura solamente representa el concepto general.

No tenemos información suficiente para calcular el área de una figura indeterminada.

Necesitamos algo concreto:

Circulo(5)

o:

Rectangulo(10, 4)

La clase abstracta define qué esperamos.

Las clases concretas definen cómo se realiza.

Una clase abstracta también puede tener métodos normales

Un error frecuente consiste en pensar que una clase abstracta solamente puede contener métodos abstractos.

No es así.

Podemos combinar ambos:

from abc import ABC, abstractmethod

class Empleado(ABC):

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

    def mostrar_nombre(self):
        return self.nombre

    @abstractmethod
    def calcular_salario(self):
        pass

mostrar_nombre() ya tiene una implementación común.

Pero:

calcular_salario()

dependerá del tipo de empleado.

class EmpleadoFijo(Empleado):

    def __init__(self, nombre, salario):
        super().__init__(nombre)
        self.salario = salario

    def calcular_salario(self):
        return self.salario

Podríamos crear otro tipo:

class EmpleadoPorHora(Empleado):

    def __init__(self, nombre, horas, tarifa):
        super().__init__(nombre)
        self.horas = horas
        self.tarifa = tarifa

    def calcular_salario(self):
        return self.horas * self.tarifa

Las clases comparten determinadas características y al mismo tiempo proporcionan su propia implementación para el comportamiento que varía.

Ejemplo práctico: sistema de pagos

Ahora utilicemos la abstracción en un escenario parecido a los que encontramos en aplicaciones reales.

Queremos aceptar diferentes métodos de pago.

Primero definimos la abstracción:

from abc import ABC, abstractmethod

class MetodoPago(ABC):

    @abstractmethod
    def pagar(self, cantidad):
        pass

Implementamos tarjeta:

class PagoTarjeta(MetodoPago):

    def pagar(self, cantidad):
        print(f"Pagando ${cantidad} con tarjeta")

Después PayPal:

class PagoPayPal(MetodoPago):

    def pagar(self, cantidad):
        print(f"Pagando ${cantidad} con PayPal")

Y transferencia:

class PagoTransferencia(MetodoPago):

    def pagar(self, cantidad):
        print(f"Pagando ${cantidad} mediante transferencia")

Podemos crear una función:

def procesar_compra(metodo_pago, total):
    metodo_pago.pagar(total)

Y utilizarla con cualquiera de nuestras implementaciones:

procesar_compra(PagoTarjeta(), 100)
procesar_compra(PagoPayPal(), 50)
procesar_compra(PagoTransferencia(), 75)

La función:

procesar_compra()

no necesita conocer los detalles específicos de cada proveedor.

Solamente espera recibir algo capaz de:

pagar()

Esto reduce la dependencia entre partes del sistema.

Abstracción y encapsulamiento: ¿son lo mismo?

No.

Esta es probablemente una de las confusiones más frecuentes cuando se estudia programación orientada a objetos.

La abstracción y el encapsulamiento están relacionados, pero solucionan problemas diferentes.

Podemos resumirlos así:

ConceptoPregunta principal
Abstracción¿Qué necesita conocer el usuario del objeto?
Encapsulamiento¿Cómo protegemos y controlamos el acceso a su estado interno?

La abstracción busca representar solamente los aspectos relevantes y proporcionar una interfaz comprensible.

El encapsulamiento agrupa estado y comportamiento y controla cómo se accede a determinados detalles internos.

Veamos un ejemplo.

Ejemplo de abstracción y encapsulamiento

Supongamos que tenemos una cuenta bancaria:

class CuentaBancaria:

    def __init__(self, saldo):
        self._saldo = saldo

    def depositar(self, cantidad):
        if cantidad > 0:
            self._saldo += cantidad

    def obtener_saldo(self):
        return self._saldo

El usuario interactúa mediante:

cuenta.depositar(100)

En lugar de necesitar conocer todos los pasos que podría realizar internamente el sistema bancario.

Ahí existe una abstracción de la operación.

Al mismo tiempo, _saldo representa un detalle interno que la clase intenta mantener controlado mediante sus métodos.

Eso se relaciona con encapsulamiento.

Una forma sencilla de recordarlo es:

La abstracción reduce lo que necesitamos comprender; el encapsulamiento controla cómo accedemos a los detalles internos.

En proyectos reales ambos principios suelen trabajar juntos.

Diferencia entre abstracción y ocultamiento de información

También es habitual utilizar “abstracción” y “ocultar información” como si fueran exactamente lo mismo.

Existe relación, pero no son conceptos idénticos.

Cuando abstraemos decidimos qué representación necesita el consumidor.

Cuando ocultamos información impedimos o desalentamos que otras partes dependan de determinados detalles internos.

Una API puede ser una abstracción aunque internamente existan numerosos componentes.

El usuario necesita conocer sus operaciones disponibles, parámetros y resultados.

No necesita conocer cada consulta SQL, estructura interna o servidor involucrado.

Abstracción y herencia

La herencia puede utilizarse para implementar determinadas abstracciones mediante jerarquías de clases.

En nuestro ejemplo:

Figura
 ├── Circulo
 ├── Rectangulo
 └── Triangulo

Figura define una idea general.

Las clases concretas heredan de ella y completan el comportamiento.

Sin embargo, es importante no confundir ambos conceptos.

La abstracción responde a cómo representamos un concepto omitiendo detalles innecesarios.

La herencia es un mecanismo de relación entre clases.

Podemos utilizar herencia para construir abstracciones, pero abstracción y herencia no significan lo mismo.

Abstracción y polimorfismo

La abstracción también suele aparecer junto al polimorfismo.

Volvamos a nuestras figuras:

figuras = [
    Circulo(3),
    Rectangulo(4, 5),
    Triangulo(10, 6)
]

for figura in figuras:
    print(figura.calcular_area())

Todos los objetos responden a:

calcular_area()

La abstracción define la operación común.

El polimorfismo permite que la misma llamada produzca un comportamiento adecuado dependiendo del objeto concreto.

Podemos decir que estos conceptos se complementan.

Los cuatro pilares de la programación orientada a objetos

Tradicionalmente se explican cuatro conceptos fundamentales de POO:

  1. Abstracción
  2. Encapsulamiento
  3. Herencia
  4. Polimorfismo

Podemos compararlos rápidamente:

PilarIdea principalEjemplo
AbstracciónMostrar lo esencialpagar()
EncapsulamientoControlar detalles internos_saldo
HerenciaCompartir estructura y comportamientoPerro(Animal)
PolimorfismoMisma interfaz, distintos comportamientoshacer_sonido()

No funcionan como cuatro técnicas completamente aisladas.

En un buen diseño orientado a objetos suelen aparecer combinadas.

Abstracción mediante interfaces

En lenguajes como Java o C# encontramos el concepto explícito de interface.

Una interfaz define operaciones que una implementación debe proporcionar.

Conceptualmente podríamos tener:

RepositorioUsuario
    guardar(usuario)
    buscar(id)
    eliminar(id)

Después:

RepositorioUsuario
 ├── RepositorioPostgreSQL
 └── RepositorioMemoria

El resto de nuestra aplicación puede trabajar con:

RepositorioUsuario

sin preocuparse excesivamente por dónde están almacenados los datos.

Python tiene un modelo más dinámico, pero las clases abstractas mediante ABC permiten expresar contratos similares en muchos escenarios.

Abstracción mediante protocolos y duck typing en Python

Python también permite trabajar con abstracciones de una forma menos rígida.

Existe una frase conocida:

Si camina como pato y hace cuac como pato, probablemente podemos tratarlo como pato.

Es la idea detrás del duck typing.

Consideremos:

class Perro:
    def hacer_sonido(self):
        return "Guau"

class Gato:
    def hacer_sonido(self):
        return "Miau"

Podemos escribir:

def reproducir_sonido(animal):
    print(animal.hacer_sonido())

Y utilizar:

reproducir_sonido(Perro())
reproducir_sonido(Gato())

Las clases no necesitan heredar de una clase Animal.

La función simplemente espera que el objeto proporcione:

hacer_sonido()

Esta flexibilidad forma parte del estilo de Python.

En código moderno con tipado estático opcional también podemos expresar contratos estructurales mediante Protocol del módulo typing.

Por ejemplo:

from typing import Protocol

class PuedeHablar(Protocol):

    def hablar(self) -> str:
        ...

Una clase compatible podría ser:

class Persona:

    def hablar(self) -> str:
        return "Hola"

No necesita heredar explícitamente de PuedeHablar para satisfacer estructuralmente ese protocolo en herramientas de análisis de tipos.

Clase abstracta vs interfaz

Aunque el significado exacto depende del lenguaje, podemos establecer una comparación conceptual.

Clase abstractaInterfaz
Puede proporcionar comportamiento comúnDefine principalmente un contrato
Puede contener estadoNormalmente se centra en capacidades
Puede tener métodos implementadosDefine operaciones esperadas
Se utiliza como base de una jerarquíaSe utiliza para desacoplar implementaciones

En Python la frontera es menos rígida que en lenguajes como Java.

Podemos utilizar:

  • ABC;
  • @abstractmethod;
  • Protocol;
  • duck typing.

La elección depende del tipo de contrato que queramos expresar.

Ejemplo de abstracción con una base de datos

Veamos por qué la abstracción resulta especialmente útil en arquitectura de software.

Supongamos que nuestro servicio registra usuarios directamente en PostgreSQL:

class ServicioUsuario:

    def registrar(self, usuario):
        # conexión PostgreSQL
        # consulta SQL
        # guardar usuario
        pass

Nuestro servicio ahora conoce demasiados detalles sobre almacenamiento.

Podemos crear una abstracción:

from abc import ABC, abstractmethod

class RepositorioUsuario(ABC):

    @abstractmethod
    def guardar(self, usuario):
        pass

Implementación concreta:

class RepositorioPostgreSQL(RepositorioUsuario):

    def guardar(self, usuario):
        print("Guardando usuario en PostgreSQL")

Y nuestro servicio:

class ServicioUsuario:

    def __init__(self, repositorio):
        self.repositorio = repositorio

    def registrar(self, usuario):
        self.repositorio.guardar(usuario)

Ahora podemos hacer:

repositorio = RepositorioPostgreSQL()
servicio = ServicioUsuario(repositorio)

servicio.registrar("Leonel")

Si después queremos utilizar otra implementación:

class RepositorioMemoria(RepositorioUsuario):

    def guardar(self, usuario):
        print("Guardando usuario temporalmente en memoria")

podemos sustituirla sin modificar la lógica principal:

servicio = ServicioUsuario(
    RepositorioMemoria()
)

Aquí podemos ver cómo la abstracción ayuda a reducir el acoplamiento.

Abstracción y dependencia de implementaciones

Observa estas dos ideas.

Primera:

ServicioUsuario -> PostgreSQL

Segunda:

ServicioUsuario -> RepositorioUsuario
                       ↑
              RepositorioPostgreSQL

En el segundo diseño, el servicio conoce el concepto:

RepositorioUsuario

pero no necesita depender directamente de todos los detalles de PostgreSQL.

Este tipo de diseño aparece constantemente en aplicaciones profesionales.

No significa que debamos crear una abstracción para absolutamente todo.

Significa que podemos utilizarlas cuando existe una variación o complejidad que merece ser aislada.

Abstracción en una API

Otro ejemplo fácil de reconocer es una API.

Imagina que utilizas:

cliente.enviar_mensaje(
    destinatario="[email protected]",
    mensaje="Hola"
)

Detrás podrían ocurrir muchas cosas:

  1. validar parámetros;
  2. autenticar una solicitud;
  3. convertir datos a JSON;
  4. abrir una conexión;
  5. realizar una petición HTTP;
  6. procesar la respuesta;
  7. gestionar errores.

Pero el consumidor trabaja con una operación sencilla:

enviar_mensaje()

Una buena API proporciona una abstracción útil sobre una operación más compleja.

De hecho, utilizamos abstracciones constantemente aunque no estemos trabajando explícitamente con clases abstractas.

Ejemplos de abstracción que usamos todos los días al programar

Considera esta línea:

archivo = open("datos.txt")

¿Necesitas conocer cada llamada que realiza el sistema operativo para acceder físicamente al almacenamiento?

Normalmente no.

O esta:

lista.sort()

Para utilizarla no necesitamos implementar el algoritmo interno de ordenamiento.

También:

print("Hola")

oculta numerosos detalles necesarios para mostrar información.

Las funciones y bibliotecas son formas de abstracción.

Por eso es importante entender que la abstracción es un concepto más amplio que simplemente utilizar ABC.

ABC es una herramienta concreta de Python para implementar determinados diseños abstractos.

Niveles de abstracción

Un sistema puede tener diferentes niveles de abstracción.

Por ejemplo, una aplicación de comercio electrónico podría trabajar así:

Interfaz de usuario
        ↓
Servicio de compras
        ↓
Servicio de pagos
        ↓
Proveedor de pagos
        ↓
Red

Cada capa puede proporcionar una abstracción a la capa superior.

La interfaz no necesita saber cómo se almacena una transacción.

El servicio de compras no necesita conocer todos los detalles de comunicación de red del proveedor.

Esta separación ayuda a manejar sistemas complejos.

Pero debemos encontrar un equilibrio.

Demasiadas capas innecesarias también pueden convertir un proyecto sencillo en algo difícil de entender.

Ventajas de utilizar abstracción

Veamos con mayor detalle sus principales beneficios.

Código más comprensible

Una buena abstracción permite pensar en conceptos relevantes para el problema.

Es más fácil leer:

pedido.calcular_total()

que encontrar repetida por todo el programa la lógica necesaria para calcular impuestos, descuentos y precios.

Menor acoplamiento

Las clases pueden depender de contratos en lugar de implementaciones específicas.

Esto facilita reemplazar componentes.

Mayor flexibilidad

Podemos agregar nuevas implementaciones sin modificar necesariamente el código que utiliza la abstracción.

Mejor mantenimiento

Los detalles internos pueden evolucionar mientras la interfaz se mantiene estable.

Pruebas más sencillas

Podemos reemplazar dependencias reales por implementaciones controladas.

Código reutilizable

Las operaciones comunes pueden ser utilizadas por diferentes clases.

Estos beneficios son especialmente visibles en sistemas que deben evolucionar durante años.

Desventajas de la abstracción

La abstracción tampoco es gratuita.

Aplicarla sin necesidad puede complicar un proyecto.

Puede aumentar la cantidad de código

En lugar de una clase podemos terminar con una interfaz, una clase abstracta y varias implementaciones.

Puede dificultar seguir el flujo

Demasiadas capas hacen que encontrar dónde ocurre realmente una operación sea más difícil.

Puede crear abstracciones incorrectas

Si agrupamos conceptos que realmente no comparten un comportamiento estable, la jerarquía terminará generando problemas.

Puede ser innecesaria

Si solamente existe una implementación sencilla y no existe una razón clara para aislarla, agregar varias capas podría no aportar valor.

Una buena regla práctica es:

Abstrae la complejidad que realmente necesitas gestionar, no la complejidad que imaginas que quizá exista algún día.

Errores comunes al aplicar abstracción

Crear clases abstractas para todo

No toda clase necesita una interfaz o clase base.

La abstracción debe solucionar un problema concreto.

Crear jerarquías demasiado profundas

Algo como:

Objeto
  ↓
Entidad
  ↓
SerVivo
  ↓
Animal
  ↓
Mamifero
  ↓
Canino
  ↓
Perro

puede ser correcto en determinados modelos, pero también puede convertirse rápidamente en una estructura difícil de mantener.

Confundir abstracción con ocultar variables

Utilizar:

_saldo

no convierte automáticamente una clase en una buena abstracción.

Exponer demasiados detalles

Una abstracción pierde valor si obliga al consumidor a comprender casi toda la implementación.

Diseñar contratos demasiado grandes

Si una abstracción exige demasiados métodos, algunas implementaciones pueden verse obligadas a proporcionar operaciones que realmente no necesitan.

En general, las abstracciones pequeñas y enfocadas suelen ser más fáciles de mantener.

¿Cuándo deberíamos utilizar una clase abstracta?

Una clase abstracta puede ser útil cuando varias clases representan diferentes versiones de un mismo concepto y queremos garantizar determinadas operaciones comunes.

Por ejemplo:

Notificacion
 ├── Email
 ├── SMS
 └── Push

Podemos definir:

from abc import ABC, abstractmethod

class Notificacion(ABC):

    @abstractmethod
    def enviar(self, mensaje):
        pass

Después cada implementación decide cómo enviar el mensaje.

Esta abstracción tiene sentido porque existe una capacidad común clara.

Ejemplo final: sistema de notificaciones

Construyamos el ejemplo completo:

from abc import ABC, abstractmethod


class Notificacion(ABC):

    @abstractmethod
    def enviar(self, destinatario, mensaje):
        pass


class NotificacionEmail(Notificacion):

    def enviar(self, destinatario, mensaje):
        print(
            f"Email enviado a {destinatario}: {mensaje}"
        )


class NotificacionSMS(Notificacion):

    def enviar(self, destinatario, mensaje):
        print(
            f"SMS enviado a {destinatario}: {mensaje}"
        )


class NotificacionPush(Notificacion):

    def enviar(self, destinatario, mensaje):
        print(
            f"Notificación push para {destinatario}: {mensaje}"
        )

Ahora creamos una función que depende de la abstracción:

def avisar_usuario(
    notificacion,
    destinatario,
    mensaje
):
    notificacion.enviar(
        destinatario,
        mensaje
    )

Podemos utilizar diferentes implementaciones:

email = NotificacionEmail()
sms = NotificacionSMS()
push = NotificacionPush()

avisar_usuario(
    email,
    "[email protected]",
    "Tu pedido fue enviado"
)

avisar_usuario(
    sms,
    "+50370000000",
    "Tu código es 1234"
)

avisar_usuario(
    push,
    "usuario-123",
    "Tienes una nueva notificación"
)

La función avisar_usuario() no conoce los detalles internos de correo, SMS o notificaciones push.

Solamente conoce una operación abstracta:

enviar()

Si mañana agregamos WhatsApp:

class NotificacionWhatsApp(Notificacion):

    def enviar(self, destinatario, mensaje):
        print(
            f"WhatsApp enviado a {destinatario}: {mensaje}"
        )

el resto del diseño puede seguir funcionando con el mismo contrato.

¿Abstracción significa escribir menos código?

No necesariamente.

En algunos casos incluso escribiremos más código inicialmente.

La ventaja aparece en la estructura.

Supongamos que tenemos tres implementaciones.

Sin una abstracción clara, cada parte del sistema podría conocer directamente los detalles de las tres.

Con una buena abstracción, muchas partes solamente necesitan conocer el contrato común.

Por eso el objetivo no es reducir líneas.

El objetivo es reducir complejidad conceptual y dependencias innecesarias.

¿Es obligatorio usar abstracción en programación orientada a objetos?

No necesitamos crear clases abstractas en cada programa orientado a objetos.

Podemos desarrollar clases normales sin utilizar ABC.

Sin embargo, la idea de abstracción aparece constantemente cuando diseñamos software.

Cada vez que definimos una interfaz sencilla para una operación compleja estamos aplicando algún grado de abstracción.

Por eso resulta más útil aprender cómo pensar en abstracciones que memorizar únicamente la sintaxis de @abstractmethod.

Cómo identificar una buena abstracción

Una abstracción útil suele responder claramente a una responsabilidad.

Por ejemplo:

Repositorio
    guardar()
    buscar()
Notificador
    enviar()
MetodoPago
    pagar()

Los nombres expresan capacidades comprensibles.

Si necesitamos explicar durante diez minutos qué representa una interfaz de dos métodos, quizá el problema no sea quien la está leyendo.

Una buena abstracción debería permitir que el código exprese mejor la intención del programa.

Abstracción en proyectos reales

En proyectos profesionales podemos encontrar abstracciones alrededor de:

  • bases de datos;
  • almacenamiento de archivos;
  • envío de correos;
  • autenticación;
  • pagos;
  • notificaciones;
  • servicios externos;
  • sistemas de caché;
  • colas de mensajes;
  • generación de reportes.

Por ejemplo, nuestra aplicación podría depender de:

Almacenamiento

y tener:

AlmacenamientoLocal
AlmacenamientoS3
AlmacenamientoGoogleCloud

La aplicación necesita guardar archivos.

No debería necesitar conocer cada detalle de cada proveedor en todas sus capas.

Ese es exactamente el tipo de complejidad que una buena abstracción puede ayudarnos a controlar.

Abstracción vs encapsulamiento vs herencia vs polimorfismo

Antes de terminar, resumamos las diferencias.

Abstracción

Se concentra en qué características y operaciones son relevantes y deja en segundo plano detalles innecesarios.

Ejemplo:

pago.pagar()

Encapsulamiento

Controla el acceso al estado y detalles internos de un objeto.

Ejemplo:

cuenta._saldo

y métodos controlados para modificarlo.

Herencia

Permite establecer relaciones donde una clase deriva de otra.

Ejemplo:

class Perro(Animal):
    pass

Polimorfismo

Permite utilizar una misma operación con diferentes implementaciones.

Ejemplo:

animal.hacer_sonido()

puede producir resultados diferentes dependiendo del objeto.

Los cuatro conceptos pueden colaborar dentro de un mismo diseño.

Preguntas frecuentes sobre abstracción en programación orientada a objetos

¿Qué es la abstracción en programación orientada a objetos?

La abstracción en POO consiste en representar las características esenciales de un objeto y proporcionar una interfaz para utilizarlo sin necesidad de conocer todos los detalles de su implementación.

¿Qué es un ejemplo de abstracción?

Una cafetera es un ejemplo cotidiano.

Presionamos un botón para preparar café sin controlar manualmente todos los procesos internos.

En programación, un método como:

cafetera.preparar_cafe()

puede ofrecer una interfaz sencilla para una operación internamente compleja.

¿Cómo se implementa la abstracción en Python?

Una opción consiste en utilizar clases abstractas mediante:

from abc import ABC, abstractmethod

Después podemos definir métodos que las clases derivadas deben implementar utilizando:

@abstractmethod

Python también permite trabajar con abstracciones mediante duck typing y Protocol.

¿Qué diferencia existe entre abstracción y encapsulamiento?

La abstracción se concentra en mostrar lo necesario para utilizar un componente sin depender de detalles irrelevantes.

El encapsulamiento controla cómo se organiza y accede al estado interno de un objeto.

Son conceptos relacionados, pero no equivalentes.

¿Qué es una clase abstracta?

Es una clase utilizada como base para definir una estructura o contrato común.

En Python una clase que hereda de ABC puede declarar métodos abstractos que las clases concretas deberán implementar.

¿Qué es ABC en Python?

ABC significa Abstract Base Class.

Forma parte del módulo abc de Python y puede utilizarse como base para definir clases abstractas.

¿Qué hace @abstractmethod?

Marca un método como abstracto.

Las clases concretas derivadas deberán proporcionar una implementación antes de poder ser instanciadas, mientras el método siga siendo abstracto en la jerarquía.

¿Una clase abstracta puede tener métodos normales?

Sí.

Una clase abstracta puede combinar métodos abstractos con métodos que ya contienen una implementación.

Esto permite compartir comportamiento común y obligar a las clases derivadas a implementar únicamente determinadas operaciones.

¿Abstracción y herencia son lo mismo?

No.

La abstracción representa únicamente los aspectos relevantes de un concepto.

La herencia es un mecanismo mediante el cual una clase puede derivar comportamiento y estructura de otra.

La herencia puede ayudarnos a implementar una abstracción, pero son conceptos diferentes.

¿Cuáles son los cuatro pilares de la POO?

Generalmente se presentan como:

  • abstracción;
  • encapsulamiento;
  • herencia;
  • polimorfismo.

Cada uno aborda un aspecto diferente del diseño orientado a objetos.

Conclusión

La abstracción en programación orientada a objetos nos permite manejar sistemas complejos concentrándonos en las características y operaciones realmente importantes para cada parte del programa.

En lugar de obligar a todo el código a conocer cada detalle de implementación, podemos definir contratos e interfaces que expresen claramente qué puede hacer un componente.

En Python podemos implementar este tipo de diseño mediante herramientas como ABC y @abstractmethod, aunque también existen enfoques más flexibles basados en duck typing y Protocol.

Lo más importante, sin embargo, no es memorizar una palabra clave.

Una buena abstracción aparece cuando conseguimos convertir algo complejo en una interfaz sencilla, coherente y útil sin ocultar información que el consumidor realmente necesita.

Cuando escribas una clase, prueba hacerte una pregunta: ¿qué necesita saber realmente quien utilice este objeto?

La respuesta suele ser un excelente punto de partida para diseñar una abstracción mejor.