En informática , la programación orientada a aspectos ( AOP ) es un paradigma de programación que tiene como objetivo aumentar la modularidad al permitir la separación de preocupaciones transversales . Lo hace agregando comportamiento al código existente (un consejo ) sin modificar el código, en lugar de especificar por separado qué código se modifica a través de una especificación de " punto de corte ", como "registrar todas las llamadas de función cuando el nombre de la función comienza con 'set ' ". Esto permite que se agreguen comportamientos que no son centrales para la lógica empresarial (como el registro) a un programa sin saturar el código de funciones centrales.
AOP incluye métodos y herramientas de programación que apoyan la modularización de preocupaciones a nivel del código fuente, mientras que el desarrollo de software orientado a aspectos se refiere a toda una disciplina de ingeniería.
La programación orientada a aspectos implica dividir la lógica del programa en áreas cohesivas de funcionalidad (las llamadas preocupaciones ). Casi todos los paradigmas de programación admiten cierto nivel de agrupación y encapsulación de preocupaciones en entidades separadas e independientes al proporcionar abstracciones (por ejemplo, funciones, procedimientos, módulos, clases, métodos) que se pueden utilizar para implementar, abstraer y componer estas preocupaciones. Algunas preocupaciones "atraviesan" múltiples abstracciones en un programa y desafían estas formas de implementación. Estas preocupaciones se denominan preocupaciones transversales o preocupaciones horizontales.
El registro ejemplifica una preocupación transversal, ya que una estrategia de registro debe afectar a cada parte registrada del sistema. Por lo tanto, el registro abarca todas las clases y métodos registrados.
Todas las implementaciones de AOP tienen algunas expresiones de corte transversal que encapsulan cada preocupación en un solo lugar. La diferencia entre las implementaciones radica en la potencia, la seguridad y la facilidad de uso de las construcciones proporcionadas. Por ejemplo, los interceptores que especifican los métodos para expresar una forma limitada de corte transversal, sin mucho soporte para la seguridad de tipos o la depuración. AspectJ tiene varias de esas expresiones y las encapsula en una clase especial, llamada aspecto . Por ejemplo, un aspecto puede alterar el comportamiento del código base (la parte no relacionada con el aspecto de un programa) aplicando un consejo (comportamiento adicional) en varios puntos de unión (puntos en un programa) especificados en una cuantificación o consulta llamada punto de corte (que detecta si un punto de unión dado coincide). Un aspecto también puede realizar cambios estructurales compatibles con binarios en otras clases, como agregar miembros o padres.
AOP tiene varios antecedentes directos A1 y A2: [1] protocolos de reflexión y metaobjetos , programación orientada a sujetos , filtros de composición y programación adaptativa. [2]
Gregor Kiczales y sus colegas de Xerox PARC desarrollaron el concepto explícito de AOP y lo siguieron con la extensión AspectJ AOP para Java. El equipo de investigación de IBM optó por un enfoque de herramientas en lugar de un enfoque de diseño de lenguajes y en 2001 propuso Hyper/J y el Concern Manipulation Environment , que no se han utilizado ampliamente.
Los ejemplos de este artículo utilizan AspectJ.
Se considera que Microsoft Transaction Server fue la primera aplicación importante de AOP, seguida de Enterprise JavaBeans . [3] [4]
Normalmente, un aspecto se encuentra disperso o enredado como código, lo que dificulta su comprensión y mantenimiento. Se dispersa porque la función (como el registro) se distribuye en varias funciones no relacionadas que podrían usar su función, posiblemente en sistemas completamente no relacionados o escritos en lenguajes diferentes. Por lo tanto, cambiar el registro puede requerir la modificación de todos los módulos afectados. Los aspectos se enredan no solo con la función principal de los sistemas en los que se expresan, sino también entre sí. Por lo tanto, cambiar un aspecto implica comprender todos los aspectos enredados o tener algún medio por el cual se pueda inferir el efecto de los cambios.
Por ejemplo, consideremos una aplicación bancaria con un método conceptualmente muy simple para transferir una cantidad de una cuenta a otra: [5]
void transfer ( Cuenta desdeAcc , Cuenta aAcc , int amount ) lanza una excepción { if ( fromAcc . getBalance () < amount ) lanza una nueva InsufficientFundsException (); fromAcc . retirar ( monto ); toAcc . depositar ( monto ); }
Sin embargo, este método de transferencia pasa por alto ciertas consideraciones que una aplicación implementada requeriría, como verificar que el usuario actual esté autorizado para realizar esta operación, encapsular las transacciones de la base de datos para evitar la pérdida accidental de datos y registrar la operación para fines de diagnóstico.
Una versión con todas esas nuevas preocupaciones podría verse así:
void transfer ( Cuenta desdeAcc , Cuenta aAcc , int cantidad , Usuario usuario , Registrador registrador , Base de datos base de datos ) lanza una excepción { logger.info ( "Transfiriendo dinero..." ) ; si ( ! isUserAuthorised ( usuario , desdeAcc )) { logger.info ( " El usuario no tiene permiso." ) ; lanzar una nueva UnauthorisedUserException (); } si ( desdeAcc.getBalance () < cantidad ) { logger.info ( "Fondos insuficientes." ) ; lanzar una nueva InsufficientFundsException ( ) ; } desdeAcc . retirar ( monto ); hastaAcc . depositar ( monto ); base de datos . commitChanges (); // Operación atómica. logger . info ( "Transacción exitosa." ); }
En este ejemplo, otros intereses se han enredado con la funcionalidad básica (a veces llamada preocupación por la lógica empresarial ). Las transacciones, la seguridad y el registro son ejemplos de preocupaciones transversales .
Ahora, pensemos en lo que sucedería si de repente tuviéramos que cambiar las consideraciones de seguridad de la aplicación. En la versión actual del programa, las operaciones relacionadas con la seguridad aparecen dispersas en numerosos métodos, y un cambio de este tipo requeriría un gran esfuerzo.
AOP intenta resolver este problema permitiendo al programador expresar preocupaciones transversales en módulos independientes llamados aspectos . Los aspectos pueden contener consejos (código unido a puntos específicos en el programa) y declaraciones entre tipos (miembros estructurales añadidos a otras clases). Por ejemplo, un módulo de seguridad puede incluir consejos que realizan una comprobación de seguridad antes de acceder a una cuenta bancaria. El punto de corte define los momentos ( puntos de unión ) en los que se puede acceder a una cuenta bancaria, y el código en el cuerpo del consejo define cómo se implementa la comprobación de seguridad. De esa manera, tanto la comprobación como los lugares se pueden mantener en un solo lugar. Además, un buen punto de corte puede anticipar cambios posteriores en el programa, por lo que si otro desarrollador crea un nuevo método para acceder a la cuenta bancaria, el consejo se aplicará al nuevo método cuando se ejecute.
Entonces, para el ejemplo anterior que implementa el registro en un aspecto:
aspecto Logger { void Banco . transferencia ( Cuenta desdeAcc , Cuenta aAcc , int monto , Usuario usuario , Logger logger ) { logger . información ( "Transfiriendo dinero..." ); } void Banco . getMoneyBack ( Usuario usuario , int transactionId , Registrador logger ) { logger . info ( "El usuario solicitó la devolución de dinero." ); } //Otro código transversal. }
Se puede pensar en AOP como una herramienta de depuración o una herramienta a nivel de usuario. Los consejos deben reservarse para los casos en los que no se puede cambiar la función (a nivel de usuario) [6] o no se desea cambiar la función en el código de producción (depuración).
El componente relacionado con el asesoramiento de un lenguaje orientado a aspectos define un modelo de punto de unión (JPM). Un JPM define tres cosas:
Los modelos de puntos de unión se pueden comparar en función de los puntos de unión expuestos, cómo se especifican los puntos de unión, las operaciones permitidas en los puntos de unión y las mejoras estructurales que se pueden expresar.
Los PCD "con tipo" coinciden con un tipo particular de punto de unión (por ejemplo, ejecución de método) y, a menudo, toman una firma similar a Java como entrada. Un punto de unión de este tipo se ve así:
ejecución ( * conjunto * ( * ))
Este punto de corte coincide con un punto de unión de ejecución de método, si el nombre del método comienza con " set
" y hay exactamente un argumento de cualquier tipo.
Los PCD "dinámicos" comprueban los tipos de ejecución y vinculan variables. Por ejemplo,
Este ( Punto )
Este punto de corte coincide cuando el objeto que se está ejecutando actualmente es una instancia de la clase Point
. Tenga en cuenta que el nombre no calificado de una clase se puede utilizar a través de la búsqueda de tipo normal de Java.
Los PCD de "alcance" limitan el alcance léxico del punto de unión. Por ejemplo:
dentro de ( com . empresa . * )
Este punto de corte coincide con cualquier punto de unión de cualquier tipo del com.company
paquete. *
Es una forma de comodín que se puede utilizar para hacer coincidir varios elementos con una sola firma.
Los puntos de corte se pueden componer y nombrar para su reutilización. Por ejemplo:
pointcut set ( ) : ejecución ( * set * ( * ) ) && este ( Punto ) && dentro ( com.empresa . * ) ;
set
" y this
es una instancia de tipo Point
en el com.company
paquete. Se puede hacer referencia a él utilizando el nombre " set()
".después de () : establecer () { Mostrar . actualizar (); }
set()
punto de corte coincide con el punto de unión, ejecutar el código Display.update()
después de que se complete el punto de unión".Existen otros tipos de JPM. Todos los lenguajes de asesoramiento se pueden definir en términos de su JPM. Por ejemplo, un lenguaje de aspecto hipotético para UML puede tener el siguiente JPM:
Las declaraciones entre tipos proporcionan una forma de expresar preocupaciones transversales que afectan la estructura de los módulos. También conocidas como clases abiertas y métodos de extensión , permiten a los programadores declarar en un solo lugar miembros o padres de otra clase, generalmente para combinar todo el código relacionado con una preocupación en un aspecto. Por ejemplo, si un programador implementó la preocupación transversal de visualización y actualización utilizando visitantes, una declaración entre tipos que utilice el patrón de visitantes podría verse así en AspectJ:
aspecto DisplayUpdate { void Point . acceptVisitor ( Visitor v ) { v . visit ( this ); } // otro código transversal... }
Este fragmento de código agrega el acceptVisitor
método a la Point
clase.
Se requiere que cualquier adición estructural sea compatible con la clase original, de modo que los clientes de la clase existente continúen operando, a menos que la implementación de AOP pueda esperar controlar a todos los clientes en todo momento.
Los programas AOP pueden afectar a otros programas de dos maneras diferentes, dependiendo de los lenguajes y entornos subyacentes:
La dificultad de cambiar de entorno significa que la mayoría de las implementaciones producen programas de combinación compatibles a través de un tipo de transformación de programa conocida como weaving . Un weaving de aspectos lee el código orientado a aspectos y genera el código orientado a objetos apropiado con los aspectos integrados. El mismo lenguaje AOP se puede implementar a través de una variedad de métodos de weaving, por lo que la semántica de un lenguaje nunca debe entenderse en términos de la implementación del weaving. Solo la velocidad de una implementación y su facilidad de implementación se ven afectadas por el método de combinación utilizado.
Los sistemas pueden implementar el entrelazado a nivel de fuente utilizando preprocesadores (como se implementó C++ originalmente en CFront ) que requieren acceso a los archivos fuente del programa. Sin embargo, la forma binaria bien definida de Java permite que los entrelazadores de bytecode funcionen con cualquier programa Java en formato de archivo .class. Los entrelazadores de bytecode se pueden implementar durante el proceso de compilación o, si el modelo de entrelazado es por clase, durante la carga de la clase. AspectJ comenzó con el entrelazado a nivel de fuente en 2001, entregó un entrelazador de bytecode por clase en 2002 y ofreció soporte avanzado en tiempo de carga después de la integración de AspectWerkz en 2005.
Cualquier solución que combine programas en tiempo de ejecución debe proporcionar vistas que los separen adecuadamente para mantener el modelo separado del programador. La compatibilidad de código de bytes de Java con varios archivos fuente permite que cualquier depurador pueda ejecutar un archivo .class correctamente entrelazado en un editor de código fuente. Sin embargo, algunos descompiladores de terceros no pueden procesar código entrelazado porque esperan código producido por Javac en lugar de todos los formatos de código de bytes admitidos (consulte también § Crítica, a continuación).
El entrelazado en tiempo de implementación ofrece otro enfoque. [7] Básicamente, esto implica posprocesamiento, pero en lugar de parchear el código generado, este enfoque de entrelazado crea subclases de clases existentes para que las modificaciones se introduzcan mediante la anulación de métodos. Las clases existentes permanecen intactas, incluso en tiempo de ejecución, y todas las herramientas existentes, como depuradores y perfiladores, se pueden utilizar durante el desarrollo. Un enfoque similar ya ha demostrado su eficacia en la implementación de muchos servidores de aplicaciones Java EE , como WebSphere de IBM .
La terminología estándar utilizada en la programación orientada a aspectos puede incluir:
Los aspectos surgieron de la programación orientada a objetos y la programación reflexiva . Los lenguajes AOP tienen una funcionalidad similar a la de los protocolos de metaobjetos , pero más restringida . Los aspectos se relacionan estrechamente con conceptos de programación como sujetos , mixins y delegación . Otras formas de utilizar paradigmas de programación orientada a aspectos incluyen filtros de composición y el enfoque de hiperslices . Desde al menos la década de 1970, los desarrolladores han estado utilizando formas de intercepción y parcheo de despacho que se asemejan a algunos de los métodos de implementación para AOP, pero estos nunca tuvieron la semántica que las especificaciones transversales proporcionan en un solo lugar. [ cita requerida ]
Los diseñadores han considerado formas alternativas de lograr la separación del código, como los tipos parciales de C# , pero dichos enfoques carecen de un mecanismo de cuantificación que permita alcanzar varios puntos de unión del código con una declaración declarativa. [ cita requerida ]
Aunque parezca que no tiene relación, en las pruebas, el uso de mocks o stubs requiere el uso de técnicas AOP, como around advice. Aquí, los objetos colaboradores son para el propósito de la prueba, una preocupación transversal. Por lo tanto, los diversos marcos de objetos Mock proporcionan estas características. Por ejemplo, un proceso invoca un servicio para obtener un monto de saldo. En la prueba del proceso, no es importante de dónde proviene el monto, sino solo que el proceso use el saldo de acuerdo con los requisitos. [ cita requerida ]
Los programadores deben poder leer y comprender el código para evitar errores. [10] Incluso con la educación adecuada, comprender las cuestiones transversales puede resultar difícil sin el soporte adecuado para visualizar tanto la estructura estática como el flujo dinámico de un programa. [11] A partir de 2002, AspectJ comenzó a proporcionar complementos IDE para respaldar la visualización de cuestiones transversales. Esas funciones, así como la asistencia y la refactorización del código de Aspect , son ahora comunes.
Dado el poder de AOP, cometer un error lógico al expresar aspectos transversales puede llevar a un fracaso generalizado del programa. Por el contrario, otro programador puede cambiar los puntos de unión en un programa, como renombrar o mover métodos, de maneras que el autor del aspecto no anticipó y con consecuencias imprevistas . Una ventaja de modularizar los aspectos transversales es permitir que un programador afecte fácilmente a todo el sistema. Como resultado, tales problemas se manifiestan como un conflicto sobre la responsabilidad entre dos o más desarrolladores por un error determinado. AOP puede acelerar la solución de estos problemas, ya que solo se debe cambiar el aspecto. Sin AOP, los problemas correspondientes pueden estar mucho más dispersos. [ cita requerida ]
La crítica más básica del efecto de AOP es que el flujo de control se oscurece, y que no sólo es peor que la muy difamada declaración GOTO , sino que es muy análoga a la declaración de broma COME FROM . [11] La falta de conocimiento de la aplicación , que es fundamental para muchas definiciones de AOP (el código en cuestión no tiene ninguna indicación de que se aplicará un consejo, que se especifica en cambio en el punto de corte), significa que el consejo no es visible, en contraste con una llamada de método explícita. [11] [12] Por ejemplo, compare el programa COME FROM: [11]
5 ENTRADA X 10 IMPRIMIR 'El resultado es:' 15 IMPRIMIR X 20 PROVIENE DE 10 25 X = X * X 30 RETORNO
con un fragmento AOP con semántica análoga:
main () { entrada x imprimir ( resultado ( x )) } entrada resultado ( int x ) { devolver x } alrededor ( int x ): llamar ( resultado ( int )) && args ( x ) { int temp = proceder ( x ) devolver temp * temp }
De hecho, el punto de corte puede depender de la condición de tiempo de ejecución y, por lo tanto, no ser estáticamente determinista. Esto se puede mitigar, pero no resolver, mediante un análisis estático y el soporte de IDE que muestra qué consejos coinciden potencialmente .
Las críticas generales son que AOP pretende mejorar "tanto la modularidad como la estructura del código", pero algunos argumentan que en cambio socava estos objetivos e impide "el desarrollo independiente y la comprensibilidad de los programas". [13] En concreto, la cuantificación por puntos de corte rompe la modularidad: "uno debe, en general, tener conocimiento de todo el programa para razonar sobre la ejecución dinámica de un programa orientado a aspectos". [14] Además, aunque sus objetivos (modularizar las preocupaciones transversales) se entienden bien, su definición real no está clara y no se distingue claramente de otras técnicas bien establecidas. [13] Las preocupaciones transversales potencialmente se cruzan entre sí, lo que requiere algún mecanismo de resolución, como el ordenamiento. [13] De hecho, los aspectos pueden aplicarse a sí mismos, lo que lleva a problemas como la paradoja del mentiroso . [15]
Las críticas técnicas incluyen que la cuantificación de los puntos de corte (que definen dónde se ejecutan los consejos) es "extremadamente sensible a los cambios en el programa", lo que se conoce como el problema frágil de los puntos de corte . [13] Los problemas con los puntos de corte se consideran intratables. Si uno reemplaza la cuantificación de los puntos de corte con anotaciones explícitas, se obtiene en cambio una programación orientada a atributos , que es simplemente una llamada explícita a una subrutina y sufre el mismo problema de dispersión, que AOP fue diseñado para resolver. [13]
Muchos lenguajes de programación han implementado AOP, dentro del lenguaje o como una biblioteca externa , incluidos:
{{cite web}}
: CS1 maint: copia archivada como título ( enlace ){{cite web}}
: CS1 maint: bot: estado de URL original desconocido ( enlace ){{cite journal}}
: Requiere citar revista |journal=
( ayuda )