Professional Documents
Culture Documents
Introducción
Separación de Incumbencias
El principio puede aplicarse de distintas maneras. Por ejemplo, separar las fases del proceso
de desarrollo puede verse como una separación de actividades de ingeniería en el tiempo y por
su objetivo. Definir subsistemas, objetos y componentes son otras formas de poner en práctica
el principio de separación de incumbencias. Por eso podemos decir que se trata de un principio
rector omnipresente en el proceso de desarrollo de software.
Las construcciones provistas por los lenguajes de programación, que fueron creados para
implementar los modelos generados por las técnicas de diseño existentes, reproducen las
jerarquías y, por lo tanto, comparten el defecto explicado en el párrafo anterior. En el
paradigma de programación imperativa, la descomposición consiste en identificar
procedimientos que resuelvan parte del problema, y la jerarquía se da en el árbol de ejecución,
según el cual los procedimientos se invocan unos a otros. En el caso de la programación
orientada a objetos, la jerarquía generada en la etapa de diseño suele plasmarse en las
relaciones de herencia o de composición entre objetos. Por ejemplo, algunos patrones de
diseño de uso habitual como observador (observer), visitante (visitor) y mediador (mediator) (2)
exhiben estos problemas, ya que para aplicarlos es necesario adaptar a ellos más de una
clase.
El problema aparece cuando una incumbencia afecta a distintas partes del sistema que no
aparecen relacionadas en la jerarquía. En ese caso, la única solución suele ser escribir código
repetido que resuelva esa incumbencia para cada subsistema.
Incumbencias Transversales
Si una misma operación tiene que acceder a varios servicios (logging, locking, presentación,
transporte, autenticación, seguridad, etc), además de cumplir con su función específica,
estamos ante una muestra de código enredado.
La fuente de estos problemas es que en realidad cada aplicación tiene una política sobre
dónde se requiere cada uno de estos servicios; pero esa política no es explícita, está oculta
como parte de la estructura del programa. Esto hace que sea difícil de entender, razonar sobre
ella y mantenerla.
Aspectos
Ya sabemos cuál es la idea detrás de un aspecto, veamos ahora cómo se llevan a la práctica.
Empecemos por ver la propuesta de AOP para resolver la dispersión de la Figura 1, que se
muestra en la Figura 3. Lo que se hizo fue definir un aspecto (pronto hablaremos de las partes
que lo conforman) que indica que se debe ejecutar la instrucción Display.update(); al terminar
la invocación a cualquier método cuyo nombre comienze con set, de una subclase de
FigureElement. La notación usada está basada en AspectJ (3).
Para entender cómo se aplica un aspecto, tenemos que empezar por el concepto de punto de
unión (join point). Los puntos de unión son puntos en la ejecución de un programa. Por
ejemplo, "al invocar al método Line.getP1() por cuarta vez". Es importante recalcar que los
puntos de unión no son posiciones en el código fuente (ni dentro de una instrucción, ni entre
instrucciones), sino en la ejecución del programa. La frase anterior habla de la cuarta
invocación de un método en una ejecución, y esto no corresponde a un punto en el código. En
distintas ejecuciones podría variar qué instrucción es la que invoca por cuarta vez a
Line.getP1(), por ejemplo, se podría llamar a este método dentro de un ciclo while (i<j)
Line.getP1(); Dependiendo de los valores de i y j, y de lo que haya pasado antes en esta
ejecución, la cuarta llamada a este método podría ser en esta instrucción o en otra.
Figura 3: Aspecto de actualización de pantalla
Ahora pasemos a hablar de un concepto un poco más complicado que el de punto de unión,
que es el de pointcut. Un pointcut es un predicado, una afirmación que es cumplida por un
conjunto de puntos de unión. En nuestro ejemplo de la Figura 3, aparece un pointcut: que
vamos a analizar por partes. call(void FigureElement+.set*(..)) significa "al invocar (call)
cualquier método de la clase FigureElement o de una sublclase (+), cuyo nombre comience
con set (set*), que tenga void como tipo de retorno y cualquier cantidad de parámetros (..)". El
siguiente fragmento es fácil de comprender: call(void FigureElement.moveBy(int, int)). El
operador de disyunción (||) se usa para unir estos dos pointcuts y formar uno nuevo. Los puntos
de unión que cumplan el pointcut compuesto serán aquellos que cumplan uno u otro de los
dos.
Además de call hay otros constructores de pointcuts, como get (al leer el valor de una
variable), set (al modificarlo), cflowbelow (dentro del flujo de control), initialization (al
inicializar un objeto), etc. Y también formas de acceder al contexto como target (clase que
recibe el mensaje), args (argumentos de una llamada), etc.
Los pointcuts son parte de un aspecto, indican dónde (en qué puntos de unión) se va a aplicar.
La otra parte es lo que se llama un consejo (advice), que indica qué es lo que hay que hacer en
esos puntos de unión. En la Figura 3, el consejo dice que hay que agregar la instrucción
Display.Update(); después (after) de cada uno de los puntos de unión indicados por el
pointcut. En vez de after, se podría haber puesto before (antes), after returning (después de
ejecutar un método en forma normal), after throwing (después de arrojar una excepción) o
around (en lugar de ejecutar el punto de unión).
El mecanismo por el cual se combinan los aspectos con el código base se llama entretejido
(weaving), y puede hacerse en distintos momentos de la vida de un programa. Una posibilidad
es llevar a cabo el entretejido en una etapa de precompilación: se toma el código base y los
aspectos, y se produce nuevo código fuente con el resultado del entretejido, insertando los
consejos en los puntos de unión correspondiente. También se puede hacer durante la
compilación, generando código objeto que cumpla la funcionalidad base más la de los
aspectos. Y otra alternativa es el llamado entretejido dinámico, por el cual se controla la
ejecución del programa y, cada vez que se llega a un punto de unión incluido en un pointcut de
un aspecto, se ejecuta el consejo asociado.
En este ejemplo tan sencillo que vimos, aparecen ya las dos ideas más poderosas que hay
detrás de los aspectos: prescindencia (obliviousness) y cuantificación (quantification). Ambas
están asociadas al mecanismo de construcción de pointcuts. La prescindencia se refiere a que
el programador encargado de implementar una funcionalidad específica no debería estar al
tanto de las otras dimensiones que pueden afectar su código. La cuantificación es la posibilidad
de indicar en qué puntos de unión se aplicará un aspecto, sin necesidad de enumerarlos uno
por uno.
Prescindencia y Cuantificación
Otras implementaciones brindan herramientas para que, por fuera del código, se señale en
forma interactiva dónde se espera que se entrelace un aspecto. Este método es más limpio que
el anterior desde el punto de vista de la prescindencia, porque la tarea puede llevarse a cabo
independientemente de la escritura del programa. Pero comparte con el anterior la violación del
principio de cuantificación: la política no se hace explícita ni se modulariza como parte de los
aspectos, está dada por las acciones del encargado de marcar a mano los puntos de unión.
Incluso si el resultado de esas acciones se almacena en algún formato, los puntos de unión
aparecen listados y no definidos por sus propiedades.
Aspectos y .NET
Una de las razones para la falta de adopción puede encontrarse en la dificultad que introducen
los aspectos para razonar sobre los programas. Muchas de las técnicas de diseño y de las
buenas prácticas de codificación actuales tienen por objetivo garantizar la composicionalidad
del razonamiento sobre programas: no hace falta leer el código entero para entender qué es lo
que hace un fragmento, se pueden extraes conclusiones sobre el comportamiento local y esas
conclusiones siguen siendo válidas sin importar cómo se inserte el fragmento en un sistema
mayor. Pero los aspectos pueden invalidar esta premisa, ya que permiten modificar "desde
afuera" el comportamiento de una parte del programa.
Existen algunas propuestas para solucionar este problema de AOP y proyectos completos de
ASoC que constituyen alternativas al enfoque de aspectos, creadas con el objetivo específico
de evitar alterar el comportamiento local o de hacerlo en forma predecible. Hasta el momento
ninguna de estas variantes ha cobrado la relevancia suficiente como para mover a la
comunidad de AOSD en esa dirección.
Además del nuestro, existen otros proyectos para agregar, de uno u otro modo, aspectos a
.NET. Teniendo en cuenta que una de las grandes ventajas de esta tecnología es la de ser
multilenguaje, es obvia la importancia que tiene, para toda propuesta sobre .NET respetar este
rasgo. Esta premisa inclina la balanza hacia las técnicas de entretejido dinámico; las variantes
estáticas son altamente dependientes del lenguaje, porque implican preprocesar o compilar el
código base. Esto complica un poco la implementación, porque el entretejido dinámico implica,
la mayoría de las veces, modificar la máquina virtual o el soporte de tiempo de ejecución de los
lenguajes (en este caso, el CLR). Tal vez por eso, como podrán observar en la página de
proyectos investigación de AOSD (5) , existen varios basados en trabajar con un único lenguaje
.NET (en general, C#, como es de esperar) o con un conjunto limitado de ellos.
También podrían ofrecerse como ejemplo los .NET Enterprise Services (como otras alternativas
de middleware), ofreciendo servicios a los programadores de componentes: seguridad basada
en roles, colas de mensajes, eventos, transacciones, etc. Todos ellos ofrecen alguna forma de
prescindencia, pero no incluyen notaciones que sirvan para cuantificación, siempre hay que
indicar caso por caso a qué componentes afectan.