En ingeniería de software y ciencias de la computación , la abstracción es el proceso de generalizar detalles concretos , [1] como atributos , alejándolos del estudio de objetos y sistemas para enfocar la atención en detalles de mayor importancia. [2] La abstracción es un concepto fundamental en ciencias de la computación e ingeniería de software , especialmente dentro del paradigma de programación orientada a objetos . [3] Algunos ejemplos de esto incluyen:
La esencia de la abstracción es preservar la información que es relevante en un contexto dado y olvidar la información que es irrelevante en ese contexto.
– Juan V. Guttag [5]
La informática funciona en gran medida de forma independiente del mundo concreto. El hardware implementa un modelo de computación que es intercambiable con otros. [6] El software está estructurado en arquitecturas que permiten a los humanos crear sistemas enormes concentrándose en unos pocos temas a la vez. Estas arquitecturas están hechas de opciones específicas de abstracciones. La décima regla de Greenspun es un aforismo sobre cómo una arquitectura de este tipo es inevitable y compleja.
La abstracción del lenguaje es una forma central de abstracción en informática: se desarrollan nuevos lenguajes artificiales para expresar aspectos específicos de un sistema. Los lenguajes de modelado ayudan en la planificación. Los lenguajes de computadora pueden procesarse con una computadora. Un ejemplo de este proceso de abstracción es el desarrollo generacional del lenguaje de programación desde el lenguaje de máquina hasta el lenguaje ensamblador y el lenguaje de alto nivel . Cada etapa puede usarse como un trampolín para la siguiente etapa. La abstracción del lenguaje continúa, por ejemplo, en los lenguajes de script y los lenguajes de programación específicos del dominio .
Dentro de un lenguaje de programación, algunas características permiten al programador crear nuevas abstracciones. Estas incluyen subrutinas , módulos , polimorfismo y componentes de software . Algunas otras abstracciones, como los patrones de diseño de software y los estilos arquitectónicos, permanecen invisibles para un traductor y operan solo en el diseño de un sistema.
Algunas abstracciones intentan limitar el rango de conceptos que un programador debe conocer, ocultando por completo las abstracciones en las que se basan. El ingeniero de software y escritor Joel Spolsky ha criticado estos esfuerzos al afirmar que todas las abstracciones tienen fugas , es decir, que nunca pueden ocultar por completo los detalles que se encuentran debajo; [7] sin embargo, esto no niega la utilidad de la abstracción.
Algunas abstracciones están diseñadas para interoperar con otras abstracciones; por ejemplo, un lenguaje de programación puede contener una interfaz de función externa para realizar llamadas al lenguaje de nivel inferior.
Los distintos lenguajes de programación ofrecen distintos tipos de abstracción, según las aplicaciones previstas para el lenguaje. Por ejemplo:
function(parameters) = 0;
abstract
interface
Los analistas han desarrollado varios métodos para especificar formalmente los sistemas de software. Algunos métodos conocidos son:
Los lenguajes de especificación generalmente se basan en abstracciones de un tipo u otro, ya que las especificaciones suelen definirse antes en un proyecto (y en un nivel más abstracto) que en una implementación final. El lenguaje de especificación UML , por ejemplo, permite la definición de clases abstractas , que en un proyecto en cascada siguen siendo abstractas durante la fase de arquitectura y especificación del proyecto.
Los lenguajes de programación ofrecen la abstracción de control como uno de los principales propósitos de su uso. Las computadoras entienden operaciones en el nivel más bajo, como mover algunos bits de una ubicación de la memoria a otra ubicación y producir la suma de dos secuencias de bits. Los lenguajes de programación permiten que esto se haga en el nivel más alto. Por ejemplo, considere esta declaración escrita en un estilo similar al de Pascal :
a := (1 + 2) * 5
Para un ser humano, esto parece un cálculo bastante simple y obvio ( "uno más dos es tres, por cinco es quince" ). Sin embargo, los pasos de bajo nivel necesarios para llevar a cabo esta evaluación y devolver el valor "15", y luego asignar ese valor a la variable "a", son en realidad bastante sutiles y complejos. Los valores deben convertirse a una representación binaria (a menudo una tarea mucho más complicada de lo que uno pensaría) y los cálculos deben descomponerse (por el compilador o intérprete) en instrucciones de ensamblaje (de nuevo, que son mucho menos intuitivas para el programador: operaciones como desplazar un registro binario a la izquierda, o sumar el complemento binario del contenido de un registro a otro, simplemente no son la forma en que los humanos piensan acerca de las operaciones aritméticas abstractas de adición o multiplicación). Finalmente, asignar el valor resultante de "15" a la variable etiquetada "a", de modo que "a" pueda usarse más tarde, involucra pasos adicionales "detrás de escena" de buscar la etiqueta de una variable y la ubicación resultante en la memoria física o virtual, almacenar la representación binaria de "15" en esa ubicación de memoria, etc.
Sin la abstracción de control, un programador tendría que especificar todos los pasos de nivel binario/registro cada vez que quisiera simplemente sumar o multiplicar un par de números y asignar el resultado a una variable. Esta duplicación de esfuerzos tiene dos consecuencias negativas graves:
La programación estructurada implica la división de tareas complejas del programa en partes más pequeñas con un claro control de flujo e interfaces entre los componentes, con una reducción del potencial de efectos secundarios de la complejidad.
En un programa simple, esto puede tener como objetivo garantizar que los bucles tengan puntos de salida únicos u obvios y (cuando sea posible) tener puntos de salida únicos de funciones y procedimientos.
En un sistema más grande, puede implicar dividir tareas complejas en muchos módulos diferentes. Consideremos un sistema que maneja la nómina en los barcos y en las oficinas en tierra:
Estas capas producen el efecto de aislar los detalles de implementación de un componente y sus métodos internos de los demás. La programación orientada a objetos adopta y extiende este concepto.
La abstracción de datos impone una separación clara entre las propiedades abstractas de un tipo de datos y los detalles concretos de su implementación. Las propiedades abstractas son aquellas que son visibles para el código del cliente que hace uso del tipo de datos (la interfaz con el tipo de datos), mientras que la implementación concreta se mantiene completamente privada y, de hecho, puede cambiar, por ejemplo, para incorporar mejoras de eficiencia con el tiempo. La idea es que dichos cambios no deben tener ningún impacto en el código del cliente, ya que no implican ninguna diferencia en el comportamiento abstracto.
Por ejemplo, se podría definir un tipo de datos abstracto denominado tabla de búsqueda que asocie de forma única las claves con los valores y en la que se puedan recuperar los valores especificando sus claves correspondientes. Una tabla de búsqueda de este tipo se puede implementar de varias maneras: como una tabla hash , un árbol de búsqueda binario o incluso una simple lista lineal de pares (clave:valor). En lo que respecta al código del cliente, las propiedades abstractas del tipo son las mismas en cada caso.
Por supuesto, todo esto depende de que los detalles de la interfaz estén correctos desde el principio, ya que cualquier cambio en ellos puede tener un impacto importante en el código del cliente. Una forma de verlo es la siguiente: la interfaz forma un contrato sobre el comportamiento acordado entre el tipo de datos y el código del cliente; todo lo que no esté detallado en el contrato está sujeto a cambios sin previo aviso.
Si bien gran parte de la abstracción de datos se realiza mediante la informática y la automatización, hay ocasiones en las que este proceso se realiza de forma manual y sin intervención de programación. Una forma de entender esto es a través de la abstracción de datos dentro del proceso de realización de una revisión sistemática de la literatura. En esta metodología, los datos son abstraídos por uno o varios abstractores al realizar un metanálisis , y los errores se reducen mediante una doble abstracción de datos seguida de una verificación independiente, conocida como adjudicación . [11]
En la teoría de la programación orientada a objetos , la abstracción implica la capacidad de definir objetos que representan "actores" abstractos que pueden realizar un trabajo, informar sobre su estado y cambiarlo, y "comunicarse" con otros objetos del sistema. El término encapsulamiento se refiere a la ocultación de los detalles del estado , pero la extensión del concepto de tipo de datos de los lenguajes de programación anteriores para asociar el comportamiento más fuertemente con los datos, y estandarizar la forma en que interactúan los diferentes tipos de datos, es el comienzo de la abstracción . Cuando la abstracción procede a las operaciones definidas, permitiendo que se sustituyan objetos de diferentes tipos, se denomina polimorfismo . Cuando procede en la dirección opuesta, dentro de los tipos o clases, estructurándolos para simplificar un conjunto complejo de relaciones, se denomina delegación o herencia .
Varios lenguajes de programación orientados a objetos ofrecen facilidades similares para la abstracción, todas para apoyar una estrategia general de polimorfismo en la programación orientada a objetos, que incluye la sustitución de un tipo por otro en el mismo rol o en uno similar. Aunque no se admite de manera tan general, una configuración, imagen o paquete puede predeterminar una gran cantidad de estos enlaces en tiempo de compilación , tiempo de enlace o tiempo de carga . Esto dejaría solo un mínimo de tales enlaces para cambiar en tiempo de ejecución .
Por ejemplo, Common Lisp Object System o Self presentan una menor distinción entre clases e instancias y un mayor uso de la delegación para el polimorfismo . Los objetos y funciones individuales se abstraen de manera más flexible para adaptarse mejor a una herencia funcional compartida de Lisp .
C++ ejemplifica otro extremo: depende en gran medida de plantillas , sobrecargas y otros enlaces estáticos en tiempo de compilación, lo que a su vez tiene ciertos problemas de flexibilidad.
Aunque estos ejemplos ofrecen estrategias alternativas para lograr la misma abstracción, no alteran fundamentalmente la necesidad de admitir sustantivos abstractos en el código: toda programación se basa en la capacidad de abstraer verbos como funciones, sustantivos como estructuras de datos o como procesos.
Consideremos, por ejemplo, un fragmento de Java de muestra para representar algunos "animales" de granja comunes a un nivel de abstracción adecuado para modelar aspectos simples de su hambre y alimentación. Define una Animal
clase para representar tanto el estado del animal como sus funciones:
clase pública Animal extiende LivingThing { ubicación privada loc ; reservasDeEnergía privadas dobles ; public boolean isHungry () { return energyReserves < 2.5 ; } public void eat ( Food food ) { // Consumir alimentos energyReserves += food.getCalories ( ); } public void moveTo ( Location location ) { // Mover a nueva ubicación this.loc = location ; } }
Con la definición anterior, se podrían crear objetos de tipoAnimaly llamar a sus métodos así:
elCerdo = nuevo Animal ( ) ; laVaca = nuevo Animal ( ); si ( elCerdo.tieneHambre ( ) ) { elCerdo.comer ( restosDeMesa ) ; } si ( laVaca.tieneHambre ( ) ) { laVaca.comer ( hierba ) ; } laVaca.moverA ( elGranero ) ;
En el ejemplo anterior, la clase Animal
es una abstracción utilizada en lugar de un animal real, LivingThing
es una abstracción adicional (en este caso una generalización) de Animal
.
Si se requiere una jerarquía más diferenciada de animales –para diferenciar, por ejemplo, aquellos que proporcionan leche de aquellos que no proporcionan nada excepto carne al final de sus vidas–, ese es un nivel intermedio de abstracción, probablemente DairyAnimal (vacas, cabras) que comerían alimentos adecuados para dar buena leche, y MeatAnimal (cerdos, novillos) que comerían alimentos para dar la mejor calidad de carne.
Una abstracción de este tipo podría eliminar la necesidad de que el programador de la aplicación especifique el tipo de alimento, de modo que podría concentrarse en el programa de alimentación. Las dos clases podrían estar relacionadas mediante herencia o ser independientes, y el programador podría definir distintos grados de polimorfismo entre los dos tipos. Estas funciones tienden a variar drásticamente entre lenguajes, pero en general cada una puede lograr todo lo que es posible con cualquiera de las otras. Una gran cantidad de sobrecargas de operaciones, tipo de datos por tipo de datos, pueden tener el mismo efecto en tiempo de compilación que cualquier grado de herencia u otros medios para lograr polimorfismo. La notación de clases es simplemente una conveniencia para el programador.
Las decisiones sobre qué abstraer y qué mantener bajo el control del codificador se convierten en la principal preocupación del diseño orientado a objetos y del análisis de dominio ; en realidad, determinar las relaciones relevantes en el mundo real es la preocupación del análisis orientado a objetos o del análisis heredado.
En general, para determinar la abstracción apropiada, uno debe tomar muchas decisiones pequeñas sobre el alcance (análisis de dominio), determinar con qué otros sistemas uno debe cooperar (análisis heredado), luego realizar un análisis orientado a objetos detallado que se expresa dentro de las restricciones de tiempo y presupuesto del proyecto como un diseño orientado a objetos. En nuestro ejemplo simple, el dominio es el corral, los cerdos y vacas vivos y sus hábitos alimenticios son las restricciones heredadas, el análisis detallado es que los codificadores deben tener la flexibilidad para alimentar a los animales con lo que esté disponible y, por lo tanto, no hay razón para codificar el tipo de alimento en la clase misma, y el diseño es una única clase Animal simple de la cual los cerdos y las vacas son instancias con las mismas funciones. Una decisión de diferenciar DairyAnimal cambiaría el análisis detallado, pero el análisis de dominio y heredado permanecería inalterado; por lo tanto, está completamente bajo el control del programador, y se llama abstracción en la programación orientada a objetos, a diferencia de la abstracción en el análisis de dominio o heredado.
Cuando se habla de semántica formal de lenguajes de programación , métodos formales o interpretación abstracta , la abstracción se refiere al acto de considerar una definición menos detallada, pero segura, de los comportamientos observados del programa. Por ejemplo, uno puede observar solo el resultado final de las ejecuciones del programa en lugar de considerar todos los pasos intermedios de las ejecuciones. La abstracción se define como un modelo concreto (más preciso) de ejecución.
La abstracción puede ser exacta o fiel con respecto a una propiedad si se puede responder a una pregunta sobre la propiedad igualmente bien en el modelo concreto o abstracto. Por ejemplo, si se desea saber cuál es el valor del resultado de la evaluación de una expresión matemática que involucra solo números enteros +, -, ×, módulo n , entonces solo se necesita realizar todas las operaciones módulo n (una forma familiar de esta abstracción es la de sacar nueves ).
Sin embargo, las abstracciones, aunque no necesariamente exactas , deberían ser sólidas . Es decir, debería ser posible obtener respuestas sólidas a partir de ellas, incluso aunque la abstracción pueda simplemente producir un resultado de indecidibilidad . Por ejemplo, los estudiantes de una clase pueden ser abstraídos por sus edades mínima y máxima; si uno pregunta si cierta persona pertenece a esa clase, uno puede simplemente comparar la edad de esa persona con las edades mínima y máxima; si su edad está fuera del rango, uno puede responder con seguridad que la persona no pertenece a la clase; si no es así, uno solo puede responder "No lo sé".
El nivel de abstracción incluido en un lenguaje de programación puede influir en su usabilidad general . El marco de dimensiones cognitivas incluye el concepto de gradiente de abstracción en un formalismo. Este marco permite al diseñador de un lenguaje de programación estudiar las compensaciones entre la abstracción y otras características del diseño, y cómo los cambios en la abstracción influyen en la usabilidad del lenguaje.
Las abstracciones pueden resultar útiles cuando se trabaja con programas informáticos, porque las propiedades no triviales de los programas informáticos son esencialmente indecidibles (véase el teorema de Rice ). En consecuencia, los métodos automáticos para derivar información sobre el comportamiento de los programas informáticos tienen que descartar la terminación (en algunas ocasiones, pueden fallar, bloquearse o no dar nunca un resultado), la solidez (pueden proporcionar información falsa) o la precisión (pueden responder "No sé" a algunas preguntas).
La abstracción es el concepto central de la interpretación abstracta . La verificación de modelos generalmente se lleva a cabo en versiones abstractas de los sistemas estudiados.
La informática suele presentar niveles (o, menos comúnmente, capas ) de abstracción, en los que cada nivel representa un modelo diferente de la misma información y los mismos procesos, pero con cantidades variables de detalle. Cada nivel utiliza un sistema de expresión que implica un conjunto único de objetos y composiciones que se aplican solo a un dominio particular. [12] Cada nivel relativamente abstracto, "superior", se basa en un nivel relativamente concreto, "inferior", que tiende a proporcionar una representación cada vez más "granular". Por ejemplo, las puertas se basan en circuitos electrónicos, el binario en puertas, el lenguaje de máquina en binario, el lenguaje de programación en lenguaje de máquina, las aplicaciones y los sistemas operativos en lenguajes de programación. Cada nivel está incorporado, pero no determinado, por el nivel inferior, lo que lo convierte en un lenguaje de descripción que es en cierta medida autónomo.
Dado que muchos usuarios de sistemas de bases de datos carecen de un conocimiento profundo de las estructuras de datos de las computadoras, los desarrolladores de bases de datos a menudo ocultan la complejidad a través de los siguientes niveles:
Nivel físico: El nivel más bajo de abstracción describe cómo un sistema almacena realmente los datos. El nivel físico describe en detalle las estructuras de datos complejas de bajo nivel.
Nivel lógico: El siguiente nivel superior de abstracción describe qué datos almacena la base de datos y qué relaciones existen entre esos datos. Por lo tanto, el nivel lógico describe una base de datos completa en términos de un pequeño número de estructuras relativamente simples. Aunque la implementación de las estructuras simples en el nivel lógico puede implicar estructuras complejas de nivel físico, el usuario del nivel lógico no necesita ser consciente de esta complejidad. Esto se conoce como independencia física de los datos . Los administradores de bases de datos , que deben decidir qué información mantener en una base de datos, utilizan el nivel lógico de abstracción.
Nivel de vista: el nivel más alto de abstracción describe solo una parte de toda la base de datos. Aunque el nivel lógico utiliza estructuras más simples, la complejidad persiste debido a la variedad de información almacenada en una base de datos grande. Muchos usuarios de un sistema de base de datos no necesitan toda esta información; en cambio, necesitan acceder solo a una parte de la base de datos. El nivel de vista de abstracción existe para simplificar su interacción con el sistema. El sistema puede proporcionar muchas vistas para la misma base de datos.
La capacidad de proporcionar un diseño de diferentes niveles de abstracción puede
Tanto el diseño de sistemas como el diseño de procesos de negocio pueden utilizar esto. Algunos procesos de diseño generan específicamente diseños que contienen varios niveles de abstracción.
La arquitectura en capas divide las preocupaciones de la aplicación en grupos apilados (capas). Es una técnica utilizada en el diseño de software, hardware y comunicaciones informáticas en la que los componentes del sistema o de la red se aíslan en capas para que se puedan realizar cambios en una capa sin afectar a las demás.