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 mediante una especificación " pointcut ", como "registrar todas las llamadas a funciones cuando el nombre de la función comienza con 'set ' ". Esto permite agregar a un programa comportamientos que no son centrales para la lógica empresarial (como el registro) sin saturar el código de funciones principales.
AOP incluye métodos y herramientas de programación que respaldan 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 coherentes de funcionalidad (las llamadas preocupaciones ). Casi todos los paradigmas de programación admiten algún 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 pueden usarse 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 porque una estrategia de registro debe afectar a cada parte registrada del sistema. De este modo, el registro abarca todas las clases y métodos registrados.
Todas las implementaciones de AOP tienen algunas expresiones transversales que encapsulan cada preocupación en un solo lugar. La diferencia entre implementaciones radica en la potencia, la seguridad y la usabilidad de las construcciones proporcionadas. Por ejemplo, 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 expresiones de este tipo y las encapsula en una clase especial, llamada aspecto . Por ejemplo, un aspecto puede alterar el comportamiento del código base (la parte de un programa que no es un aspecto) aplicando consejos (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 determinado 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 siguió un enfoque de herramienta en lugar de un enfoque de diseño de lenguaje y en 2001 propuso Hyper/J y Concern Manipulation Environment , que no han tenido un uso generalizado.
Los ejemplos de este artículo utilizan AspectJ.
Se considera que Microsoft Transaction Server es la primera aplicación importante de AOP, seguida de Enterprise JavaBeans . [3] [4]
Normalmente, un aspecto está disperso o enredado como código, lo que dificulta su comprensión y mantenimiento. Está disperso porque la función (como el registro) se distribuye entre una serie de funciones no relacionadas que podrían usar su función, posiblemente en sistemas completamente no relacionados o escritos en diferentes idiomas. Por lo tanto, cambiar el registro puede requerir modificar todos los módulos afectados. Los aspectos se enredan no sólo con la función principal de los sistemas en los que se expresan, sino también entre sí. Por lo tanto, cambiar una preocupación implica comprender todas las preocupaciones enredadas o tener algún medio por el cual se pueda inferir el efecto de los cambios.
Por ejemplo, considere una aplicación bancaria con un método conceptualmente muy simple para transferir una cantidad de una cuenta a otra: [5]
anular la transferencia ( Cuenta de Cuenta , Cuenta a Cuenta , monto int ) lanza una excepción { si ( de Cuenta . getSaldo () < cantidad ) lanza una nueva excepción de fondos insuficientes (); de Acc . retirar ( cantidad ); a Acc . cantidad del depósito ) ; }
Sin embargo, este método de transferencia pasa por alto ciertas consideraciones que requeriría una aplicación implementada, 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 con fines de diagnóstico.
Una versión con todas esas nuevas preocupaciones podría verse así:
anular la transferencia ( Cuenta de Acc , Cuenta a Acc , monto int , Usuario usuario , Registrador , Base de datos ) arroja una excepción { logger . info ( "Transfiriendo dinero..." ); if ( ! isUserAuthorised ( usuario , fromAcc )) { registrador . info ( "El usuario no tiene permiso." ); lanzar una nueva excepción de usuario no autorizada (); } if ( fromAcc . getBalance () < monto ) { registrador . info ( "Fondos insuficientes." ); lanzar una nueva InsufficientFundsException (); } de Acc . retirar ( cantidad ); a Acc . cantidad del depósito ) ; base de datos . Cometer cambios (); // Operación atómica. registrador . info ( "Transacción exitosa." ); }
En este ejemplo, otros intereses se han enredado con la funcionalidad básica (a veces denominada preocupación por la lógica empresarial ). Las transacciones, la seguridad y el registro son ejemplos de preocupaciones transversales .
Ahora considere 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 tal cambio requeriría un gran esfuerzo.
AOP intenta resolver este problema permitiendo al programador expresar inquietudes transversales en módulos independientes llamados aspectos . Los aspectos pueden contener consejos (código unido a puntos específicos del programa) y declaraciones entre tipos (miembros estructurales agregados a otras clases). Por ejemplo, un módulo de seguridad puede incluir consejos que realicen un control 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 de aviso define cómo se implementa el control de seguridad. De esta manera, tanto el cheque como las plazas se pueden mantener en un solo lugar. Además, un buen pointcut 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 Registrador { void Bank . transferir ( Cuenta de Acc , Cuenta a Acc , monto int , Usuario usuario , Registrador registrador ) { registrador . info ( "Transfiriendo dinero..." ); } Banco nulo . getMoneyBack ( Usuario usuario , int ID de transacción , registrador registrador ) { registrador . info ( "El usuario solicitó la devolución del dinero." ); } // Otro código transversal. }
Se puede pensar en AOP como una herramienta de depuración o una herramienta a nivel de usuario. Se deben reservar consejos para los casos en los que no se puede cambiar la función (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 consejos 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 "afines" 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. Uno de esos puntos se ve así:
ejecución ( * establecer * ( * ))
Este punto de acceso 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" verifican los tipos de tiempo de ejecución y vinculan variables. Por ejemplo,
este punto )
Este punto de acceso coincide cuando el objeto que se está ejecutando actualmente es una instancia de clase Point
. Tenga en cuenta que el nombre no calificado de una clase se puede utilizar mediante la búsqueda de tipos 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 muchas cosas con una sola firma.
Los puntos de corte se pueden componer y nombrar para su reutilización. Por ejemplo:
set de corte de punto () : ejecución ( * set * ( * ) ) && este ( Punto ) && dentro de ( 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 () : establecer () { Mostrar . actualizar (); }
set()
punto de corte coincide con el punto de unión, ejecute el código Display.update()
después de que se complete el punto de unión".Hay otros tipos de JPM. Todos los idiomas 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 manera de expresar preocupaciones transversales que afectan la estructura de los módulos. También conocido como clases abiertas y métodos de extensión , esto permite a los programadores declarar en un 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 actualización de visualización usando visitantes, una declaración entre tipos usando el patrón de visitante podría verse así en AspectJ:
aspecto DisplayUpdate { punto vacío . aceptarVisitante ( Visitante v ) { v . visitar ( esto ); } // 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, según los lenguajes y entornos subyacentes:
La dificultad de cambiar entornos significa que la mayoría de las implementaciones producen programas combinados compatibles a través de un tipo de transformación de programa conocido como tejido . Un tejedor de aspectos lee el código orientado a aspectos y genera código orientado a objetos apropiado con los aspectos integrados. El mismo lenguaje AOP se puede implementar mediante una variedad de métodos de tejido, por lo que la semántica de un lenguaje nunca debe entenderse en términos de la implementación del tejido. Sólo 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 tejido a nivel de código fuente utilizando preprocesadores (como C++ se implementó originalmente en CFront ) que requieren acceso a los archivos fuente del programa. Sin embargo, la forma binaria bien definida de Java permite a los tejedores de códigos de bytes trabajar con cualquier programa Java en formato de archivo .class. Los tejedores de código de bytes se pueden implementar durante el proceso de construcción o, si el modelo de tejido es por clase, durante la carga de clases. AspectJ comenzó con el tejido a nivel de fuente en 2001, entregó un tejedor de código de bytes por clase en 2002 y ofreció soporte avanzado de 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 segreguen adecuadamente para mantener el modelo segregado del programador. El soporte de código de bytes de Java para múltiples archivos fuente permite que cualquier depurador recorra un archivo .class correctamente tejido en un editor de código fuente. Sin embargo, algunos descompiladores de terceros no pueden procesar código tejido porque esperan código producido por Javac en lugar de todas las formas de código de bytes admitidas (consulte también § Crítica, a continuación).
El tejido en tiempo de implementación ofrece otro enfoque. [7] Esto básicamente implica un posprocesamiento, pero en lugar de parchear el código generado, este enfoque de tejido 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 generadores de perfiles, 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 por ejemplo WebSphere de IBM .
La terminología estándar utilizada en la programación orientada a aspectos puede incluir:
Aspectos surgidos 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 temas , 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 interceptación y distribución de parches que se asemejan a algunos de los métodos de implementación de AOP, pero nunca tuvieron la semántica que las especificaciones transversales proporcionan en un solo lugar. [ cita necesaria ]
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 necesaria ]
Aunque pueda parecer no relacionado, en las pruebas, el uso de simulacros o stubs requiere el uso de técnicas de AOP, como consejos generales. Aquí los objetos colaboradores son, a efectos de la prueba, una preocupación transversal. Por lo tanto, los diversos marcos de objetos simulados proporcionan estas características. Por ejemplo, un proceso invoca un servicio para obtener un saldo. En la prueba del proceso, no importa de dónde proviene el monto, sino sólo que el proceso utilice el saldo de acuerdo con los requisitos. [ cita necesaria ]
Los programadores deben poder leer y comprender el código para evitar errores. [10] Incluso con una educación adecuada, comprender las preocupaciones transversales puede resultar difícil sin el apoyo 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 inquietudes transversales. Esas características, así como la asistencia de código de aspecto y la refactorización , ahora son comunes.
Dado el poder del POA, cometer un error lógico al expresar la transversalidad puede llevar al fracaso generalizado del programa. Por el contrario, otro programador puede cambiar los puntos de unión en un programa, como cambiando el nombre o moviendo métodos, de maneras que el autor del aspecto no anticipó y con consecuencias imprevistas . Una ventaja de modularizar las preocupaciones transversales es permitir que un programador afecte fácilmente a todo el sistema. Como resultado, estos problemas se manifiestan como un conflicto sobre la responsabilidad entre dos o más desarrolladores por un fallo determinado. AOP puede acelerar la solución de estos problemas, ya que sólo se debe cambiar el aspecto. Sin AOP, los problemas correspondientes pueden estar mucho más extendidos. [ cita necesaria ]
La crítica más básica al efecto de AOP es que el flujo de control está oscurecido y que no sólo es peor que la tan difamada declaración GOTO , sino que es muy similar a la declaración en broma COME FROM . [11] El olvido 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 en cambio se especifica en el punto), significa que el consejo no es visible, en contraste a una llamada a un método explícito. [11] [12] Por ejemplo, compare el programa COME FROM: [11]
5 ENTRADA X 10 IMPRIMIR 'El resultado es :' 15 IMPRIMIR X 20 VIENEN DE 10 25 X = X * X 30 VOLVER
con un fragmento AOP con semántica análoga:
principal () { entrada x imprimir ( resultado ( x )) } entrada resultado ( int x ) { retorno x } alrededor ( int x ): llamada ( resultado ( int )) && args ( x ) { int temp = continuar ( x ) temperatura de retorno * temperatura }
De hecho, el punto de corte puede depender de la condición del 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 soporte IDE que muestre qué consejos potencialmente coinciden.
Las críticas generales son que AOP pretende mejorar "tanto la modularidad como la estructura del código", pero algunos responden que, en cambio, socava estos objetivos e impide "el desarrollo independiente y la comprensibilidad de los programas". [13] Específicamente, la cuantificación mediante 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, si bien sus objetivos (modular las preocupaciones transversales) se entienden bien, su definición real no es 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 la ordenación. [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 pointcuts se consideran intratables. Si se reemplaza la cuantificación de puntos de corte con anotaciones explícitas, se obtiene programación orientada a atributos , que es simplemente una llamada explícita a subrutina y sufre el mismo problema de dispersión, para el cual AOP fue diseñado. [13]
Muchos lenguajes de programación han implementado AOP, dentro del lenguaje o como una biblioteca externa , incluidos:
{{cite web}}
: Mantenimiento CS1: copia archivada como título ( enlace ){{cite web}}
: Mantenimiento CS1: bot: estado de la URL original desconocido ( enlace ){{cite journal}}
: Citar diario requiere |journal=
( ayuda )