JS: Abstracción de datos
Teoría: Diseño en capas
Consideremos otro sistema sencillo: números racionales y las operaciones que se pueden realizar con ellos. El número racional es aquel que puede ser representado como una fracción a/b, donde a es el numerador de la fracción y b es el denominador de la fracción. Además, b no puede ser cero, ya que la división por cero no está permitida.
Los números racionales no son compatibles en JS, así que crearemos una abstracción para ellos nosotros mismos. Como siempre, necesitaremos un constructor y selectores:
Usando tres funciones, hemos definido un número racional. Una función (el constructor) lo construye a partir de sus partes, mientras que las otras dos funciones (los selectores) permiten extraer cada una de esas partes. Desde el punto de vista del lenguaje, lo que num representa es totalmente irrelevante. Puede ser una función (y eso es posible), un array, un objeto o incluso una cadena de texto en la implementación interna:
Aunque ahora podemos representar números racionales, esta abstracción tiene poco uso por sí sola. La abstracción se vuelve útil cuando podemos operar con ella. Para los números racionales, las operaciones básicas incluyen las operaciones aritméticas como la suma, resta o multiplicación. La multiplicación de números racionales es la operación más simple. Para llevarla a cabo, simplemente multiplicamos los numeradores y los denominadores:
La parte más interesante comienza durante la implementación. Si asumimos que la estructura real de un número racional se ve así: { numer: 2, denom: 3 }, entonces, técnicamente, la solución podría ser la siguiente:
Desde la perspectiva del código que lo llama, todo parece normal y la abstracción está preservada. Los números racionales se pasan como entrada a mul y se devuelve un número racional como salida. Sin embargo, internamente no hay ninguna abstracción; el manejo de los números racionales se basa en el conocimiento de su estructura. Cualquier cambio en la implementación interna de los números racionales requerirá la reescritura de todas las operaciones que trabajan con ellos directamente, es decir, sin el uso de selectores o constructores. Este código viola el principio de abstracción de un solo nivel (single layer abstraction).
Durante el desarrollo de sistemas complejos, se emplea el enfoque del diseño en capas. Este enfoque implica estructurar el sistema en niveles sucesivos, donde cada nivel se construye combinando partes que, en ese nivel, se consideran elementales. Las partes que se construyen en cada nivel funcionan como elementos elementales en el siguiente nivel.
El diseño en capas impregna toda la técnica de construcción de sistemas complejos. Por ejemplo, en el diseño de computadoras, los resistores y transistores se combinan (y se describen utilizando un lenguaje de circuitos analógicos), y a partir de ellos se construyen elementos como AND, OR y similares, que sirven como la base del lenguaje de circuitos digitales. A partir de estos elementos se construyen procesadores, buses y sistemas de memoria, que a su vez sirven como componentes en la construcción de computadoras utilizando lenguajes adecuados para describir la arquitectura computacional. Las computadoras, al combinarse, dan lugar a sistemas distribuidos, que se describen utilizando lenguajes de descripción de interacciones en red, y así sucesivamente. (c) SICP
En nuestro ejemplo, el nivel base está compuesto por los tipos integrados en el propio lenguaje: números y objetos. Sobre esta base, se ha construido un nivel para representar números racionales: makeRational, getDenom, getNumer. Luego, hay otro nivel donde se implementan las operaciones aritméticas sobre los números racionales: suma, resta, multiplicación, etc.
Ten en cuenta que estamos hablando sobre la implementación de los propios niveles. Por ejemplo, la operación de suma se basa completamente en el constructor y los selectores, pero no sabe ni puede saber nada sobre la estructura interna de los números racionales. Por otro lado, esto no significa que en un mismo lugar no puedan aparecer funciones de diferentes niveles. Esto es normal en muchos casos. Por ejemplo: