3 - Abstracciones: ADT (Tipos de Datos Abstractos)

Los Tipos de Datos Abstractos (ADT, por sus siglas en inglés) describen el “contrato” que debe cumplir una estructura de datos: qué operaciones ofrece, qué resultados se esperan y cuáles son las reglas de uso. Funcionan como una capa conceptual que separa la idea del contenedor de su implementación concreta.

Trabajar con ADT nos permite razonar sobre el comportamiento de los datos sin atarnos a detalles como punteros, nodos o bloques contiguos. Una vez que definimos el contrato, podemos elegir distintas estructuras que lo cumplan según las necesidades de rendimiento, memoria o complejidad.

Esta abstracción es especialmente útil en equipos grandes: documentamos el contrato una vez y luego cada implementación concreta (para escritorio, móvil o backend) simplemente debe respetarlo. También facilita las migraciones entre lenguajes, porque las operaciones lógicas permanecen invariantes.

3. Abstracciones: ADT (Tipos de Datos Abstractos)

Las abstracciones existen para simplificar. En lugar de pensar en bytes alineados o registros enlazados, describimos el conjunto de operaciones esenciales (insertar, eliminar, consultar) y los invariantes que siempre se deben cumplir. Esto acelera la fase de diseño, favorece la reutilización de código y facilita la documentación compartida entre equipos.

En muchos lenguajes modernos, los ADT se modelan con interfaces, protocolos o clases abstractas. Las estructuras concretas implementan esa interfaz sólo cuando pueden garantizar el comportamiento prometido.

3.1 ¿Qué es un ADT?

Un ADT es una definición formal que especifica:

  • Dominio de valores: el conjunto de elementos válidos (por ejemplo, elementos de una lista o nodos de un grafo).
  • Operaciones disponibles: qué acciones se permiten, qué parámetros reciben y qué devuelven.
  • Reglas o axiomas: propiedades que garantizan coherencia (por ejemplo, “el próximo elemento de una cola es el más antiguo”).

La clave es que un ADT no explica cómo lograrlo; sólo define qué debe suceder. Así separamos preocupaciones: un equipo puede concentrarse en la interfaz y otro en la implementación.

Al documentar ADT también especificamos el costo esperado de cada operación. Esto orienta a quienes deban reemplazar o mejorar la implementación: saben qué garantías deben mantener para no romper el contrato.

Por ejemplo, el ADT Conjunto indica que no puede haber elementos repetidos y que la pertenencia debe verificarse con rapidez. Ninguna de estas reglas dice que necesitemos una tabla hash o un árbol balanceado, pero esa elección surge al comparar los costos y restricciones de cada implementación.

3.2 Operaciones esperadas vs implementación concreta

La distinción entre el “qué” y el “cómo” permite mejorar una aplicación sin cambiar el código que la usa. Si definimos una interfaz Cola con operaciones encolar y desencolar, podemos pasar de un arreglo circular a una lista enlazada o a una cola de dos pilas sin tocar el resto del sistema.

También hace posible escribir código genérico: un algoritmo que recibe una pila no necesita saber si internamente usa un arreglo o una lista. Mientras el contrato se cumpla, el algoritmo funcionará igual.

Algunas consideraciones frecuentes al comparar la especificación con la implementación:

  • Complejidad temporal: la especificación puede exigir que una operación sea constante o logarítmica. Si una implementación no cumple ese límite, rompe el contrato implícito.
  • Restricciones de concurrencia: un ADT puede requerir operaciones atómicas o versiones inmutables para entornos multihilo, lo que limita las implementaciones posibles.
  • Garantías adicionales: orden estable, ausencia de duplicados, prioridades, etc. Estas reglas definen la diferencia entre estructuras similares.

Separar operaciones de implementaciones también ayuda a escribir pruebas unitarias: verificamos el contrato con casos genéricos y luego los ejecutamos contra cada estructura concreta.

3.3 Ejemplos clásicos de ADT

Algunos ADT fundamentales aparecen en casi todo lenguaje o biblioteca estándar. Cada uno define un contrato reconocido que los desarrolladores identifican al instante:

  • Lista: colección ordenada que permite insertar, eliminar y acceder por posición. Puede implementarse con arreglos dinámicos o listas enlazadas, y es ideal cuando se necesita preservar el orden de ingreso.
  • Pila: la pila es una estructura LIFO (Last In, First Out) con operaciones push y pop. Suele representarse con arreglos, listas o inclusive con una sola variable y enlaces implícitos; se aplica en historiales de deshacer y en el manejo de llamadas recursivas.
  • Cola: la cola es una estructura FIFO (First In, First Out) donde los elementos salen en el orden en que entraron. Implementaciones comunes incluyen arreglos circulares y listas con punteros a extremos, muy usadas para gestionar impresoras o tareas en servidores.
  • Árbol: un árbol es una estructura jerárquica de nodos conectados. El ADT define operaciones como insertar hijos, recorrer en profundidad o buscar manteniendo relaciones padre-hijo; resulta clave para modelar jerarquías, rutas de archivos o indices.
  • Grafo: un grafo es el conjunto de nodos y aristas donde el ADT se centra en agregar vértices, conectar pares y consultar la existencia de caminos o pesos, permitiendo representar redes sociales, mapas o dependencias de paquetes.

Estos ADT también inspiran variantes especializadas: colas de prioridad, pilas persistentes, árboles balanceados, grafos dirigidos o etiquetados. Todos heredan el contrato básico y agregan reglas adicionales.

3.4 ¿Cómo un ADT puede tener múltiples implementaciones?

La gracia de un ADT es precisamente permitir varias implementaciones según el contexto. Algunas estrategias habituales:

  • Elegir representaciones internas distintas: una lista puede usar un vector dinámico (mejor acceso aleatorio) o una lista doblemente enlazada (mejor inserción en cualquier posición).
  • Optimizar para casos de uso específicos: si la mayoría de operaciones son lecturas, una pila inmutable que comparta nodos reduce copias; si predominan escrituras, una versión mutable evita asignaciones extras.
  • Adaptar al hardware o al entorno: en GPUs conviene usar estructuras contiguas; en sistemas distribuidos, representaciones que permitan particionar o replicar datos.
  • Componer estructuras: una cola de prioridad puede construirse con un heap binario, un heap binomial o varias colas ordenadas, cada una con compromisos distintos.

Gracias a esa flexibilidad, un mismo programa puede comenzar con una implementación simple y, cuando crece la demanda, reemplazarla por otra sin modificar la lógica de negocio. Lo único que debe mantenerse intacto es el contrato del ADT.

En equipos maduros, es habitual documentar cada implementación indicando sus fortalezas y debilidades (latencia, memoria, concurrencia). Así cualquier desarrollador puede elegir la versión adecuada para su contexto sin replantear el diseño global.