Programação Orientada a Aspectos - Técnicas e paradigmas para a modularização de software. Dissertação de Mestrado apresentada por Luís Filipe Félix de Sá Sob orientação do Prof. Doutor Ramiro Gonçalves e do Prof. Doutor Carlos Rabadão Universidade de Trás-os-Montes e Alto Douro Departamento de Engenharias 2008 II "Controlling complexity is the essence of computer programming." (Brian Kernigan) III Dissertação apresentada por Luís Filipe Félix de Sá à Universidade de Trás-os-Montes e Alto Douro para cumprimento dos requisitos necessários à obtenção do grau de Mestre em Informática, sob a orientação do Prof. Doutor Ramiro Manuel Ramos Moreira Gonçalves, Professor Auxiliar do Departamento de Engenharias da Universidade de Trás-os-Montes e Alto Douro e co-orientada pelo Prof. Doutor Carlos Manuel da Silva Rabadão, Professor Adjunto do Departamento de Engenharia Informática da Escola Superior de Tecnologia e Gestão do Instituto Politécnico de Leiria. IV Aos meus Pais. V AGRADECIMENTOS A todos os meus amigos e colegas Francisco Silva, Marta Pimenta, Diana Santos, Isabel Marcelino, Andreia Penedo, Ana Oliveira, Rúben Pedro, Rodrigo Selada, Bruno Alves, Márcia, Tiago Conde pela amizade, dedicação e apoio constantes. Aos meus orientadores Professor Doutor Ramiro Gonçalves e Professor Doutor Carlos Rabadão e Co-Orientador Professor Marco Monteiro, pela partilha de conhecimentos e experiência. A todos o meu muito obrigado! Luís Filipe Félix de Sá VI RESUMO O desenvolvimento de software é uma tarefa complexa, onde se decompõe um problema localizando todas as suas preocupações. As tecnologias existentes para a implementação destas são limitadas devido à sua inadequação para as modularizar. Assim, deu-se o aparecimento de uma nova tecnologia, a Programação Orientada a Aspectos. Esta tecnologia não vem substituir mas sim estender as tecnologias existentes, melhorando-as, permitindo novas formas de modularidade, diminuindo o tempo de desenvolvimento e aumentando a reutilização de software. VII ABSTRACT Software development is a complex task, where a problem is decomposed identifying all its concerns. The current technologies for implementing them are unable to modularize all of them. Therefore, a new technology was developed, Aspect Oriented Programming. This technology doesn’t come to replace rather to improve current technologies, with new ways to achieve software modularity, resulting in a decrease of developing time and increased software reuse. VIII INDICE GERAL AGRADECIMENTOS ................................................................................................................ V RESUMO ..................................................................................................................................... VI ABSTRACT ............................................................................................................................... VII INDICE GERAL ...................................................................................................................... VIII LISTA DE TABELAS .............................................................................................................. XII LISTA DE FIGURAS .............................................................................................................. XIII ACRÓNIMOS ............................................................................................................................XV 1. 2. INTRODUÇÃO .................................................................................................................... 1 1.1 ENQUADRAMENTO TECNOLÓGICO ......................................................... 2 1.2 OBJECTIVOS E CONTRIBUTOS FUNDAMENTAIS .................................. 4 1.3 ESTRUTURAÇÃO DA DISSERTAÇÃO ......................................................... 5 TÉCNICAS E PARADIGMAS DA PROGRAMAÇÃO................................................... 7 2.1 PARADIGMA DE PROGRAMAÇÃO.............................................................. 7 2.2 LINGUAGEM DE PROGRAMAÇÃO ............................................................. 8 2.3 FORMAS DE SEPARAÇÃO.............................................................................. 8 2.3.1 Abstracção ........................................................................................................ 9 2.3.2 Ocultação de informação .............................................................................. 10 IX 2.3.3 Encapsulamento ............................................................................................. 10 2.4 PROGRAMAÇÃO ESTRUTURADA ............................................................. 10 2.5 PROGRAMAÇÃO ORIENTADA A OBJECTOS ......................................... 13 2.5.1 Linguagens POO ............................................................................................ 14 2.6 PROGRAMAÇÃO ORIENTADA A COMPONENTES ............................... 15 2.7 PROGRAMAÇÃO PÓS-OBJECTOS ............................................................. 18 2.8 META-PROGRAMMING .................................................................................. 21 2.9 SUBJECT-ORIENTED PROGRAMMING ...................................................... 22 2.10 PROGRAMAÇÃO ADAPTATIVA ................................................................. 24 2.11 FILTROS DE COMPOSIÇÃO ........................................................................ 25 2.12 PROGRAMAÇÃO ORIENTADA A ASPECTOS. ........................................ 26 2.12.1 AspectJ .......................................................................................................... 28 3. 2.13 SEPARAÇÃO MULTIDIMENSIONAL DE CONCERNS ............................ 33 2.14 DESENVOLVIMENTO DE SOFTWARE ORIENTADO A ASPECTOS.. 34 2.15 COMUNIDADES E SITUAÇÃO EM PORTUGAL ...................................... 35 2.16 DESAFIOS, PROBLEMAS E OPORTUNIDADES ...................................... 36 PROGRAMAÇÃO ORIENTADA A ASPECTOS .......................................................... 39 3.1 COMPORTAMENTOS GLOBAIS ................................................................. 39 3.2 ANÁLISE DO PROBLEMA ............................................................................ 41 3.3 SISTEMA MODULAR ..................................................................................... 46 3.4 SISTEMA POA .................................................................................................. 47 3.5 METODOLOGIA DSOA.................................................................................. 49 3.6 ANATOMIA DA LINGUAGEM POA ............................................................ 50 X 3.7 ESPECIFICAÇÃO DAS LINGUAGENS ....................................................... 51 3.7.1 Software concerns .......................................................................................... 51 3.7.2 Regras de combinação ................................................................................... 51 3.8 COMPOSIÇÃO DE SISTEMAS...................................................................... 52 3.8.1 Weaving ........................................................................................................... 52 3.8.2 Weaver ............................................................................................................. 53 3.9 BENEFÍCIOS DA POA .................................................................................... 57 3.10 DESENVOLVIMENTO DE SOFTWARE ORIENTADO A ASPECTOS.. 59 3.10.1 Identificação de concerns ............................................................................. 60 3.10.2 Implementação .............................................................................................. 60 3.11 DEVENVOLVIMENTO UTILIZANDO ASPECTJ...................................... 61 3.11.1 Análise ........................................................................................................... 62 3.11.2 @AspectJ ....................................................................................................... 62 3.11.3 Ferramentas de Desenvolvimento ............................................................... 63 3.12 POSTSHARP ..................................................................................................... 63 3.12.1 PostSharp LAOS .......................................................................................... 65 3.12.2 Análise da Tecnologia .................................................................................. 67 3.12.3 PostSharp Core ............................................................................................. 69 3.12.4 AspectJ vs. Postsharp LAOS ....................................................................... 69 3.12.5 Conclusões ..................................................................................................... 73 3.13 MODULARIZAÇÃO DE PADRÕES DE DESENHO .................................. 74 3.13.1 Exemplo Observer Pattern. ......................................................................... 76 3.13.2 Análise dos Estudos ...................................................................................... 77 XI 3.14 MODULARIZAÇÃO DE EXCEPÇÕES ........................................................ 78 3.15 FRAMEWORKS ............................................................................................... 81 3.16 MÉTRICAS ........................................................................................................ 81 4. CONSIDERAÇÕES FINAIS ............................................................................................. 86 5. BIBLIOGRAFIA ................................................................................................................ 90 6. ANEXOS ............................................................................................................................. 95 XII LISTA DE TABELAS Tabela 1 – Cronograma temporal da investigação sobre separação de concerns ......................... 20 Tabela 2 - comparação da nomenclatura do AspectJ e Postsharp Laos. ....................................... 65 Tabela 3 – Comparação de pointcuts entre PostSharp Laos e AspectJ......................................... 71 Tabela 4 - Comparação de pointcuts entre PostSharp Laos e AspectJ ......................................... 71 Tabela 5 – comparação de tipos .................................................................................................... 71 Tabela 6 – Resumo das comparações dos padrões de desenho. ................................................... 77 Tabela 7 – Evolução da utilização de POO e POA, segundo as métricas de Sant’Anna.............. 83 Tabela 8 - Comparação de métricas entre Java e AspectJ primeira parte. .................................... 85 Tabela 9 - Comparação de métricas entre Java e AspectJ segunda parte. .................................... 85 XIII LISTA DE FIGURAS Figura 1 - Módulos com acesso concorrente aos dados. ............................................................... 12 Figura 2 - Hierarquia de classes .................................................................................................... 13 Figura 3 – Espalhamento de Concerns. ........................................................................................ 19 Figura 4 - Meta-programming. ..................................................................................................... 22 Figura 5 - Subject-oriented programming. ................................................................................... 23 Figura 6 - Filtros de composição. ................................................................................................. 26 Figura 7 – Diagrama do exemplo.................................................................................................. 30 Figura 8 - Interacção entre Módulos. ............................................................................................ 40 Figura 9 - Sobreposição de unidades elementares. ....................................................................... 40 Figura 10 - Comportamentos Globais do Sistema ........................................................................ 41 Figura 11 - Metodologia POA. ..................................................................................................... 50 Figura 12 – Weaver de código fonte. ............................................................................................ 53 Figura 13 - Weaver de código intermédio..................................................................................... 54 Figura 14 - Comunicação normal entre objectos. ......................................................................... 55 Figura 15 - Comunicação utilizando uma proxy........................................................................... 56 Figura 16 – Adição de comportamentos adicionais através de proxys. ........................................ 57 XIV Figura 17 - Sistema de Desenvolvimento de Software Orientado a Aspectos. ............................ 59 Figura 18 – Incisão do estudo de Early-Aspects. .......................................................................... 60 Figura 19 – Processo de integração do Postsharp. ........................................................................ 64 Figura 20 – Padrão de Desenho Observer implementado em POO.............................................. 75 Figura 21 – Padrão de desenho Observer implementado em POA .............................................. 76 Figura 22 - Modelo de Qualidade. ................................................................................................ 83 XV ACRÓNIMOS Ao longo desta dissertação vão ser utilizadas algumas abreviaturas e designações que apenas serão apresentadas quando forem utilizadas pela primeira vez: POA Programação Orientada a Aspectos POO Programação Orientada a Objectos DSOA Desenvolvimento de Software Orientado a Aspectos POC Programação Orientada a Componentes GOF Padrões de Desenho Gang-of-Four JVM Java Virtual Machine IDE Intregrated Development Envionment CME Concern Manipulation Envionment 1 1. INTRODUÇÃO Em 1997 foi proposto um novo paradigma de programação, a Programação Orientada a Aspectos (POA), cujo principal objectivo era apresentar uma extensão aos paradigmas existentes até à data, retendo os pontos onde estes demonstraram a sua eficiência, o desenvolvimento das funcionalidades base de um sistema. Apresentando uma nova forma de modularidade onde os antigos paradigmas manifestavam reais fragilidades, esta nova extensão incide especialmente nos comportamentos transversais ao sistema (crosscutting concerns) [Kiczales, Lamping et al. 1997]. Este novo paradigma propõe a decomposição dos sistemas pelas suas variadas preocupações dividindo o software em concerns (preocupações) base (funcionalidades base do sistema) e transversais (funcionalidades globais ao sistema). É introduzido um novo conceito, o aspecto, que consiste numa unidade modular que possui uma visão global do sistema, o que lhe permite fazer a coordenação e comunicação entre concerns. Para estes, este processo é transparente. A transferência da coordenação e comunicação entre diferentes tipos de concerns para os aspectos, permite-os libertar de questões globais ao sistema tornando-os independentes. A composição de concerns é efectuada através de um compilador, Weaver, cujo resultado é um sistema onde existe uma clara separação e abstracção entre todos os concerns que definem o comportamento do sistema. O sistema torna-se claro de ler, escrever e manter, as 2 funcionalidades adicionais são simples de acoplar ao sistema e o desenho inicial é mais consistente com a sua implementação concreta. Outras tecnologias com objectivos semelhantes foram desenvolvidas: Programação Adaptativa [Lieberherr 1996], Filtros de Composição [Mehmet Aksit 1994], Subject Oriented Programming [Harrison and Ossher 1993], Separação Multidimensional de Concerns [Tarr, Ossher et al. 1999] e Programação Gerativa [Czarnecki, Eisenecker et al.]. A separação avançada de concerns1 2, a Programação Pós-Objectos [Elrad, Filman et al. 2001; Hayes 2003] e o Desenvolvimento de Software Orientado a Aspectos3 4 (DSOA) são as áreas de investigação em que a POA se enquadra. 1.1 ENQUADRAMENTO TECNOLÓGICO A separação de concerns é um princípio fundamental que determina a resolução de um concern de cada vez [Dijkstra 1976]. Em engenharia de software, o princípio da sua separação está normalmente relacionado com a decomposição e modularização de sistemas [Parnas 1972]: os sistemas de software complexos devem ser decompostos em unidades modulares menores e claramente separadas, cada uma lidando com um único concern. Sendo de destacar como benefícios a sua melhor compreensibilidade e um maior potencial para a evolução e reutilização em outros sistemas de software complexos. Os mecanismos de composição e abstracção que sucessivas gerações de linguagens de programação oferecem, evoluíram para permitir a 1 http://www.cs.ubc.ca/~kdvolder/Workshops/OOPSLA2001/ASoC.html (Acedido, Julho 2008) http://www.research.ibm.com/hyperspace/workshops/icse2001/index.htm (Acedido, Julho 2008) 3 http://www.aosd.net/ (Acedido, Julho 2008) 4 http://www.aosd-europe.net/ (Acedido, Julho 2008) 2 3 resolução de problemas do mundo real de forma mais natural, assim como, para possibilitar o alcance da separação de concerns ao nível do código-fonte. A Programação Orientada a Objectos (POO) tem sido a tecnologia de programação dominante sendo os seus benefícios amplamente reconhecidos. No entanto, a POO possui algumas limitações no tratamento de concerns, nomeadamente no que respeita aos requisitos que envolvam restrições globais e propriedades sistémicas, como é exemplo: a sincronização, a persistência, o tratamento de erros, entre outros. Estas limitações necessitam de uma tecnologia que permita uma rotura, ou seja, algo que esteja realmente para além dos objectos [Booch 2001]. Relativamente à POA, esta é uma tecnologia em evolução que oferece suporte a um novo tipo de separação de concerns no nível do código-fonte. É vista como sendo evolucionária porque considera as melhorias reconhecidas da sua separação, proporcionadas pelas tecnologias anteriores (principalmente, mas não apenas, a POO) enquanto oferece suporte a novos mecanismos para lidar com alguns concerns especiais que não são tratados adequadamente por essas tecnologias. A ideia central por trás da POA é que os mecanismos de abstracção e composição das tecnologias existentes não são suficientes para separar alguns concerns especiais encontrados em sistemas mais complexos. Denominados de crosscutting concerns, pois afectam a modularidade de vários elementos “penetrando" os seus limites, estes, sem os meios apropriados para a separação e a modularização, tendem a ficar espalhados e entrelaçados com outros concerns. Como consequências naturais reporte-se uma menor compreensibilidade, uma menor capacidade de evolução e uma menor reutilização dos artefactos do código. Esta tecnologia utiliza aspectos para alcançar a modularização de crosscutting concerns e fornece mecanismos para a sua combinação em pontos bem definidos. 4 A POA e a maioria dos trabalhos existentes sobre a separação avançada de concerns tratam do fenómeno de crosscutting no nível de implementação, tendo recentemente muitos grupos de pesquisa começado a discutir a função dos aspectos em outras actividades do processo de desenvolvimento de software. Deste modo, o Desenvolvimento de Software Orientado a Aspectos (DSOA) revela-se uma área emergente cujo objectivo, é promover a separação avançada de concerns ao longo de todo o ciclo de vida do desenvolvimento de software. O Design Orientado a Aspectos (DOA) é assim, uma parte crítica do DSOA que se concentra em notações, técnicas e ferramentas para a identificação, a estruturação, a representação e a gestão de crosscutting concerns no projecto e arquitectura de software, oferecendo um linhagem desde os requisitos até a implementação com POA. 1.2 OBJECTIVOS E CONTRIBUTOS FUNDAMENTAIS O principal objectivo deste projecto de investigação é apresentar um documento, com vista a descrever paradigmas e técnicas que se propõem a melhorar a separação de concerns. São expostas as desvantagens da sua não separação, como é possível alcançar essa separação enumerando as suas vantagens e desvantagens. Este trabalho propõe-se ainda a detalhar princípios, metodologias e práticas de algumas linguagens e tecnologias POA, recorrendo à exemplificação prática, a exemplos de aplicações dessas tecnologias e à apresentação de estudos de investigação sobre análise de resultados empíricos, utilizando métricas bem conhecidas e documentando as principais ferramentas, linguagens e a sua forma de utilização. 5 1.3 ESTRUTURAÇÃO DA DISSERTAÇÃO Inicia-se este trabalho com uma pequena apresentação deste estudo, seguindo-se o seu enquadramento, bem como, os objectivos definidos para o seu prosseguimento. Desta forma no Capitulo 2 apresenta-se uma breve evolução das várias técnicas e paradigmas que contribuíram para o aparecimento e desenvolvimento do Paradigma Orientado a Aspectos. Descrevendo-se as técnicas e tecnologias que com mais ou menos complexidade permitem alcançar uma melhor modularidade nos sistemas de software. No Capítulo 3 é desenvolvido de forma prática o Paradigma Orientado a Aspectos, linguagens de programação e aplicações práticas do paradigma. Apresenta-se o Paradigma Orientado a Aspectos, o que é, que problemas se pretendem resolver e como pode ser implementado. É apresentada a linguagem AspectJ e, de seguida, a tecnologia PostSharp. Algumas aplicações da Programação Orientada a Aspectos nomeadamente a modularização de padrões de desenho, a modularização de excepções e estudos que apresentam métricas para possibilitar a quantificação empírica de programas POA também são apresentadas e descritas. Finalmente no 4º e último Capítulo exibem-se as principais conclusões deste projecto de investigação. 7 2. TÉCNICAS E PARADIGMAS DA PROGRAMAÇÃO No âmbito do objectivo deste trabalho de projecto, procurou-se aprofundar os conhecimentos relativos às diversas técnicas e paradigmas que possibilitam resolver o problema da separação de concerns. Para este fim tornou-se necessário a análise de uma variadíssima fonte bibliográfica alusiva a esta temática, possibilitando-nos assim, expor uma visão mais holística do estudo aqui em análise. 2.1 PARADIGMA DE PROGRAMAÇÃO Um paradigma da programação pode ser definido como um “…conjunto de teorias e métodos que representam uma forma particular de se tentar organizar o conhecimento numa dada área” [Kuhn 1970], ou como, “modelos ou exemplos, e abordagens organizacionais, que permitem conceptualizar o que se deve entender por computações, bem como se devem estruturar e organizar as tarefas que se pretendem ver realizadas por um computador” [Floyd 1979]. Um paradigma da programação permite disponibilizar ao programador uma visão e uma forma de raciocinar de modo a que este consiga desenvolver e executar um programa de computador, podendo-se definir este último, como uma colecção de instruções na forma de 8 código e de variáveis que possibilitam a um computador manipular uma determinada informação. No âmbito deste projecto entende-se paradigma segundo a visão de Floyd. 2.2 LINGUAGEM DE PROGRAMAÇÃO Uma linguagem de programação possibilita que um programador especifique precisamente sobre quais dados, um computador vai actuar, como estes deverão ser armazenados ou transmitidos e que acções devem ser tomadas sob as mais variadas circunstâncias. Assim, linguagem de programação é entendida como um método normalizado para expressar instruções para um computador, ou seja, é um conjunto de regras sintácticas e semânticas usadas para definir um programa de computador. 2.3 FORMAS DE SEPARAÇÃO A evolução das linguagens de programação permitiu abstrair os programadores dos pormenores das plataformas e das máquinas em que os programas são executados, possibilitando mecanismos superiores de composição e abstracção, onde estes se ajustam aos problemas de uma forma mais natural, numa visão centrada no problema. Esta visão permite uma separação natural do problema por todos os seus concerns [Dijkstra 1976]. A separação de um sistema de software em módulos aumenta a flexibilidade, a compreensibilidade e diminui o tempo de desenvolvimento. Um módulo pode ser assim entendido como um pedaço de software independente, que faz parte de um programa de 9 computador, funcionando como uma caixa negra onde do exterior só é possível interagir com a sua interface (forma de ligação com o interior) [Parnas 1972]. Nesta continuidade, os módulos independentes podem ser desenvolvidos separadamente com informação mínima sobre outros módulos. As equipas de desenvolvimento que são atribuídas a cada módulo focam-se na sua especialidade (segurança, base de dados, etc.), o que provoca uma diminuição do tempo de desenvolvimento. Os módulos internamente podem ser redesenhados e implementados novamente para acomodar as alterações necessárias, sem que isto afecte o sistema, tornando-o flexível à evolução. A possibilidade de analisar e desenhar o sistema módulo a módulo torna-o mais compreensível. A sua separação pode ser alcançada através de vários conceitos tais como abstracção, ocultação de informação e encapsulamento como se poderá verificar nas secções seguintes. 2.3.1 Abstracção Estabelecida como um processo que extrai detalhes importantes de um item e suprime os não importantes, a abstracção também pode ser vista como um modelo ou visão que representa um item - por exemplo, as pessoas podem dizer “diga-me só os pontos-chave”, este facto representa uma forma de abstracção. Podemos ver abstracção como uma técnica que dita que informação é mais importante que outra, mas não especifica como lidar com a menos importante [Berard 2002]. 10 2.3.2 Ocultação de informação O conceito de ocultação de informação para a construção de módulos é comum ser confundido com abstracção. Como anteriormente referido, a abstracção permite identificar a informação e posteriormente ocultar a menos importante, utilizando para este efeito técnicas de ocultação de informação disponibilizadas pelas linguagens de programação (funções, subrotinas, classes, etc.) [Berard 2002]. 2.3.3 Encapsulamento Designa-se por encapsulamento o agrupamento de um ou mais itens, não sendo definido a forma como ele é delimitado. Este processo não é o mesmo que o da ocultação de informação. Embora a informação esteja encapsulada, esta pode não estar escondida - por exemplo, uma estrutura encapsula uma determinada quantidade de informação mas esta pode ainda ser cedida, embora possam ser definidas regras para restringir esse acesso. Dito isto, o encapsulamento permite agrupar informação e definir qual ocultar ou qual tornar visível [Berard 2002]. 2.4 PROGRAMAÇÃO ESTRUTURADA Tendo-se iniciado para efectuar cálculos numéricos, os programas de computador começaram por ser desenvolvidos em código máquina através da introdução de novas linguagens de programação, entre elas o Fortran [Backus, Beeber et al. 1957], tornando possível elaborar sistemas de software mais complexos sem recorrer a este intrincado código. 11 Com o aumento da complexidade do processo de desenvolvimento de software levantaram-se problemas de fiabilidade e adaptabilidade, sugerindo-se assim, a eliminação das instruções goto das linguagens de programação de alto nível, substituindo-as por estruturas de sequência, de divisão e de iteração [Dijkstra 1968]. A utilização das instruções goto pode ter efeitos devastadores uma vez que podem surgir problemas devido ao facto de esta instrução alterar o fluxo do programa. Todavia, o seu comportamento não pode ser avaliado ao longo da fase de implementação utilizando um conjunto de manipulações matemáticas e lógicas que permitam demonstrar a existência de erros [Dijkstra 1976]. Enumeram-se assim, como principais Linguagens de Programação Estruturada: Fortran [Backus, Beeber et al. 1957], Pascal [Wirth 1971] e C [Kernighan and Ritchie 1988]. As principais características da Programação Estruturada consistem em: • design descendente: decompõe-se o problema por etapas ou estruturas hierárquicas; • recursos abstractos (simplicidade): consiste em decompor acções complexas noutras mais simples, capazes de serem resolvidas com maior facilidade, ou seja, as acções complexas são abstraídas em subrotinas ou em funções; • estruturas básicas: o estrutura sequencial: a cada acção segue-se outra acção sequencialmente, isto é, a saída de uma acção é a entrada em outra acção; o estruturas de divisão: avaliam condições lógicas e, em função do resultado das mesmas, realizam uma acção ou outra; o estruturas de iteração: são sequências de instruções que se repetem um determinado número de vezes. 12 Para melhor alcançar a separação de concerns foi sugerida a introdução de critérios para a decomposição do sistema em módulos de maneira a permitir uma melhor flexibilidade e compreensibilidade do sistema, definindo quais os métodos mais efectivos da decomposição em módulos utilizando técnicas de abstracção e ocultação de informação fornecidas pela Programação Estruturada [Parnas 1972]. Todavia é de referir a existência de problemas na Programação Estruturada, sendo de destacar: o código espalhado das invocações aos vários módulos independentes, bem como, o facto de os módulos abstraírem os procedimentos, as instruções, mas não conseguirem abstrair o acesso aos dados (figura 1) resultando tal situação em programas maiores, menos flexíveis e mais difíceis de desenvolver e de reutilizar ao longo do tempo. Figura 1 - Módulos com acesso concorrente aos dados. (Adaptada de [Booch 1994]) 13 2.5 PROGRAMAÇÃO ORIENTADA A OBJECTOS Nesta visão, o programa descreve estruturas e comportamentos de objectos e classes de objectos. Um objecto encapsula dados passivos e disponibiliza operações activas sobre esses dados, a estrutura dos dados do objecto define o seu estado e os métodos definem o seu comportamento. A execução de um programa orientado a objectos é a troca de mensagens entre objectos que modificam o seu estado [Booch 1994]. Na POO o problema é decomposto em classes e em objectos pertencentes ao domínio do problema, onde os dados e os procedimentos já não estão separados e os detalhes da sua interacção são encapsulados em objectos [Booch 1994]. Alguns fundamentos da POO são a herança e o polimorfismo, onde a primeira possibilita aos objectos herdar propriedades, permitindo a reutilização de comportamentos, de outros objectos. A existência de hierarquia de objectos (figura 2) facilita a compreensão do problema e, através do polimorfismo, permite-se o tratamento desses objectos de forma igual. O encapsulamento faculta assim a modificação do conteúdo interno dos objectos sem que essas alterações afectem outros objectos. Figura 2 - Hierarquia de classes 14 Em suma, alguns princípios usados na POO são: o Princípio open closed: especifica que o desenho e desenvolvimento do código deve permitir a extensão de novas funcionalidades (open) e minimizar alterações aos módulos existentes (closed). O sistema abre-se a novas funcionalidades mas deve estar encerrado a alterações que provocam incompatibilidades a módulos existentes; o Princípio da substituição de Liskov [Liskov and Wing 2001]: possibilita substituir referências de classes base por uma outra classe que derive desta sem afectar o normal funcionamento do módulo; o Princípios da inversão da dependência: módulos hierarquicamente superiores não devem depender de módulos hierarquicamente inferiores. Todos devem depender de abstracções e esta última não deve depender de detalhes. Enquanto que os detalhes devem depender de abstracções. 2.5.1 Linguagens POO Remontando à história da Linguagens POO dá-se especial destaque aos inícios dos anos 60 com o aparecimento da linguagem de programação Simula 67, criada por Ole-Johan Dahl e Kristen Nygaard. Nos anos 70 inicia-se o desenvolvimento do Smalltalk na Xerox PARC, alguns anos depois Bjarne Stroustrup cria o C++ e finalmente na década de 90 foi criado na empresa Sun o Java por James Gosling. 15 2.6 PROGRAMAÇÃO ORIENTADA A COMPONENTES A Programação Orientada a Componentes (POC) permite elaborar programas a partir de componentes de software pré-fabricados contidos em blocos de código executáveis. Os componentes têm de seguir padrões específicos predefinidos, incluindo a inclusão de uma interface e uma gestão de versões para garantir uma boa reutilização. O objectivo da POC, passa assim por desenvolver software combinando componentes pré-fabricados. Enquanto a POO dá ênfase a classes e objectos, a POC enfatiza interfaces e composição. Os clientes dos componentes não necessitam de possuir conhecimentos de como é que um componente é implementado, enquanto as interfaces de ligação continuam inalteradas, os clientes não serão afectados por alterações na implementação do componente [Szyperski 1998]. Brown sumaria algumas definições do que é um componente [Brown and Wallnau 1998]: “A component is a nontrivial, nearly independent, and replaceable part of a system that fulfills a clear function in the context of a well-defined architecture. A component conforms to and provides the physical realization of a set of interfaces.” (Philippe Krutchen, Rational Software) “A runtime software component is a dynamically bindable package of one or more programs managed as a unit and accessed through documented interfaces that can be discovered at runtime.” (Gartner Group) “A software component is a unit of composition with contractually specified interfaces and explicit context dependencies only. A software component can be deployed independently and is subject to third-party composition.” (Clemens Szyperski) 16 “A business component represents the software implementation of an “autonomous” business concept or business process. It consists of the software artifacts necessary to express, implement, and deploy the concept as a reusable element of a larger business system.” (Wojtek Kozaczynski, SSA) “A software component is a piece of self-contained, self-deployable computer code with well-defined functionality and can be assembled with other components through its interface.” [Brown and Wallnau 1998] Um componente de software é um bloco de código executável, pronto a usar, com funcionalidades bem definidas, que pode ser combinado com outros componentes através de uma interface. Deste modo, um componente não é um objecto, mas disponibiliza recursos para os instanciar. Muitas vezes, um único componente disponibiliza interfaces para várias classes interrelacionadas, daí a POC focar-se na arquitectura e encapsulamento (packging) ao invés da intercooperação entre objectos[Szyperski, Bosch et al. 1999]. Alguns dos seus princípios são [Löwy 2003]: o Separações da interface da implementação: alterações na implementação não devem ser reflectidas na interface de ligação com os clientes; o Compatibilidade entre binários: na POC os binários, geralmente não estão compilados com o código da aplicação, mas sim separadamente e são invocados pela aplicação. É permitido que os componentes possam ser substituídos por novas versões, deste modo deverá existir compatibilidade dos binários entre as várias versões; 17 o Independência da linguagem: a escolha de uma linguagem de programação não deve impedir o uso de um componente; o Transparência da localização: os componentes podem existir no mesmo processo do programa ou noutro local. Para os componentes, o lugar onde estes são executados deve ser transparente; o Gestão de concorrência: como não é possível prever o uso que os componentes irão ter por parte dos clientes, dever-se-á implementá-los de forma a prever o pior caso possível. Para tal, os componentes devem implementar mecanismos de sincronização de acesso; o Controlo de versões: novas versões de componentes não deverão afectar as aplicações dos clientes existentes. Deve existir uma tecnologia que permita a evolução dos componentes mas que ao mesmo tempo permita o acesso às suas versões mais antigas; o Segurança com base em componentes: os componentes devem apresentar garantias aos clientes de que estes não são comprometidos por ataques; Exemplos de algumas tecnologias que aplicam este paradigma: COM5 - Component Object Model J2EE6 - Java 2 Platform, Enterprise Edition CORBA7 - Common Object Request Bro-ker: Architecture and Specifications COM+8 - Component Object Model Plus 5 6 7 http://www.microsoft.com/com/default.mspx (Acedido em Junho 2008) http://java.sun.com/j2ee/overview.html (Acedido em Junho 2008) http://www.omg.org/gettingstarted/history_of_corba.htm (Acedido em Junho 2008) 18 DCOM9 - Distributed Component Object Model ActiveX10 .NET Framework11 2.7 PROGRAMAÇÃO PÓS-OBJECTOS A POO apesar dos seus benefícios, apresenta algumas lacunas principalmente no que concerne aos requisitos globais e às propriedades sistémicas. Não é possível neste tipo de concerns decompô-los e organizá-los num único local. Assim estes tendem a estar espalhados e entrelaçados com outros concerns[Elrad, Filman et al. 2001], tais como: a sincronização, as restrições em tempo real, a persistência e a recuperação de falhas [Hürsch and Lopes 1995]. Embora seja possível identificá-los na fase conceptual, poucas linguagens de programação, permitem mantê-los separados na fase de implementação. Por exemplo, é pretendido que num programa, quando existir uma actualização de uma linha ou de um ponto, essa alteração seja reflectida imediatamente no ecrã invocando um método (figura 3). Aponta-se a existência de diversas alternativas que permitem este comportamento, nomeadamente, a introdução de código no local onde são feitas as alterações dos valores. Para cada classe é introduzido o código necessário nos métodos para actualizar o ecrã. Este código adicional secundário tem como objectivo implementar um concern que é global ao sistema. É intrusivo nas classes, para além de se repetir de igual forma por várias classes. 8 http://www.microsoft.com/com/default.mspx (Acedido em Junho 2008) http://www.microsoft.com/com/default.mspx (Acedido em Junho 2008) 10 http://www.microsoft.com/com/default.mspx (Acedido em Junho 2008) 11 http://www.microsoft.com/net/ (Acedido em Junho 2008) 9 19 Figura 3 – Espalhamento de Concerns. (Adaptada de [Kiczales, Hilsdale et al. 2001]) Programar vários concerns ao mesmo tempo e ao mesmo nível, implica que o programador tenha de saber necessariamente todos os detalhes das áreas a desenvolver. Não existe uma forma de tornar as áreas independentes e separar as tarefas. Neste âmbito, nos anos 90 surgiram várias tentativas de simplificar a crescente complexidade do software, essencialmente a adição de novas necessidades a um projecto existente [Lopes 2002]. Desta forma, a POA surgiu com o ideal de que um sistema de software é superiormente programado especificando separadamente concerns, propriedades ou áreas de interesse do sistema e a descrição dos relacionamentos entre eles [Elrad, Filman et al. 2001]. Todavia é com Gregor Kiczales liderando uma equipa no centro de pesquisa de Palo Alto (PARC, subsidiária da Xerox na Califórnia nos Estados Unidos da América) que se desenvolve o conceito de POA. Este grupo começou por efectuar pesquisas na área da reflexão [Smith 1982] e meta-object protocols [Kiczales, des Rivieres et al. 1991], desenvolvendo algumas ferramentas e 20 linguagens orientadas a aspectos tais como: AML, RG [Mendhekar, Kiczales et al. 1997] e DJ [Lopes 1997] que culminaram na linguagem AspectJ [Kiczales, Hilsdale et al. 2001; Kiczales, Hilsdale et al. 2001] que hoje em dia é considerada a linguagem padrão da POA. Porém, outra tecnologia foi apresentada em 1999 no Centro de Pesquisa de T.J. Watson da empresa IBM, Hypersapces [Ossher and Tarr 1999; Tarr, Ossher et al. 1999; Ossher and Tarr 2001] como uma evolução de subject-oriented programming [Harrison and Ossher 1993]. Na tabela 1, aponta-se as principais datas que marcaram o aparecimento de tecnologias sobre separação de concerns. Ano 1972 1976 1970s 1980s 1992 1993 1995 1996 1997 1999 2000 2001 2002 2002- … Evento Parnas, decomposição de sistemas em módulos. Dijkstra, Separação de concerns. Programação Estruturada Programação Orientada a Objectos Adaptive Programming (Lieberherr, Northeastern University) Composition Filters (Aksit, Twente University) Subject-Oriented Programming (Harrison e Ossher, IBM) Artigo - Separation of Concerns (HÄurch e Lopes) Programação Orientada a Aspectos (Kiczales, Xerox PARC) Primeira Workshop sobre Programação Orientada a Aspectos (ECOOP 97) Artigo - Programação orientada a Aspectos (Kiczales etal.) Linguagem de Programação AspectJ Tese - D: A Language Framework for Distributed Programming (Lopes) Artigo - Multi-dimensional Separation of Concerns (Tarr, Harrison e Ossher, IBM) Linguagem de Programação Hyper/J Primeira Workshop sobre Advanced Separation of Concerns (OOPSLA ) Artigo Aspect-Oriented Programming is Quantification and Obliviousness (Filman e Friedman) Edição Especial da ACM sobre Programação orientada a Aspectos. Primeira conferência Internacional sobre Desenvolvimento de Software Orientado a Aspectos (DSOA) Evolução nas várias subáreas da DSOA Tabela 1 – Cronograma temporal da investigação sobre separação de concerns 21 2.8 META-PROGRAMMING Este paradigma tem como base a reflexão computacional [Smith 1985; Maes 1987] que permite que um sistema mantenha informação sobre si e use essa informação para alterar o seu comportamento [Maes 1987]. No paradigma meta-programing [Okamura and Ishikawa 1994 ], um sistema está dividido em dois níveis: base-level e meta-level (figura 4). Os construtores básicos da linguagem de programação, classes ou invocação de objectos, são descritos no base-level e podem ser estendidos ou redefinidos pela meta-programming. Cada objecto é associado com um meta-object através de um meta-link. O meta-object é responsável pela semântica das operações no objecto base. A interface entre o programa, base-level, e as camadas de nível superior é efectuada por meta-object protocols. Este último estabelece interfaces para a linguagem de programação que permitem ao programador modificar o comportamento da linguagem de programação e a implementação. Os objectos base-level são responsáveis pelos algoritmos básicos do programa e os seus comportamentos são melhorados e adaptados através de meta-level objects de acordo com requisitos específicos. Através da captura de mensagens enviadas e recebidas para os objectos, meta-level objects, estes conseguem efectuar o trabalho de concerns [Hürsch and Lopes 1995]. 22 Figura 4 - Meta-programming. (Adaptada de [Okamura and Ishikawa 1994 ]) 2.9 SUBJECT-ORIENTED PROGRAMMING Subject-Oriented programming é uma extensão para a POO possibilitar a composição de diferentes visões subjectivas do mesmo objecto, facultando o desenvolvimento de aplicações separadamente, bem como, efectuar posteriormente a sua composição. Estes objectos contêm assim, propriedades e comportamentos específicos que ao longo do tempo e com a evolução necessitam de ser alterados. Estas propriedades e comportamentos são subjectivas ao tipo de objectivo a que se destinam, o mesmo objecto pode ter representações diferentes dependendo do tipo de objectivo com que é idealizado e da entidade que o idealiza (figura 5). 23 Deste modo, a Subject-oriented programimng pretende facilitar o desenvolvimento e evolução de aplicações que colaboram entre si. Esta colaboração é alcançada partilhando subjects (objectos) que contribuem para a execução de operações. O desenvolvimento das aplicações é efectuado em separado e sem dependência entre aplicações, permitindo estender sem modificar o original [Harrison and Ossher 1993]. A composição combina hierarquias de classes a fim de produzir novos subjects que incorporam funcionalidades de subjects existentes. A Subject-Oriented programming oferece ferramentas de composição a fim de integrar os sujeitos [Kaplan, Ossher et al. 1996]. Esta tecnologia evoluiu para se tornar a Separação Multidimensional de Concerns (secção 2.13). Figura 5 - Subject-oriented programming. (adaptada de [Harrison and Ossher 1993]) 24 2.10 PROGRAMAÇÃO ADAPTATIVA A Programação Adaptativa [Lieberherr 1996; Lopes 1997] é um modelo de programação com base em padrões de código, onde estes podem ser classificados em diferentes categorias, cada um capturando um tipo diferente de concern. Posteriormente os padrões de código são implementados como componentes de software de alto nível que interagem com os outros componentes de forma muito solta através da resolução de nomes [Lopes 2002]. Perante problemas como a elaboração de uma nova operação ou regra de negócio num sistema, é necessário colocar todo o código num só método, numa só classe, ou adopta-se a divisão do código da operação por várias classes. Porém o problema deste último método relaciona-se com a dependência na estrutura das classes, o que vai adicionar complexidade nos métodos e dificultar a evolução. A Programação Adaptativa resolve esta questão utilizando padrões de propagação, abstraindo o comportamento do concern num local e a estrutura das classes noutro. O comportamento é expresso a um alto nível utilizando uma estratégia transversal [Lieberherr, Orleans et al. 2001]. Nesta continuidade, a Programação Adaptativa pode ser definida em dois blocos: um bloco de estrutura implementado por um grafo do dicionário de classes, que contém a relação entre classes e um segundo bloco comportamental, implementado por um padrão de propagação que é depois composto com o grafo e que assim possibilita a interacção de forma flexível entre ambos. 25 2.11 FILTROS DE COMPOSIÇÃO Filtros de composição são uma extensão à POO através da adição de filtros ortogonais e modulares que permitem aumentar a adaptabilidade e a reutilização dos objectos [Aksit, Wakita et al. 1993; Bergmans and Aksit 2001]. Assim, são elaborados filtros que filtram as mensagens enviadas e recebidas pelos objectos e efectuam acções através da definição de condições para a aceitação ou rejeição de uma mensagem. Esta tecnologia consiste principalmente no interface e na implementação. Na parte de interface faz-se o tratamento das mensagens de chegada e de saída, composto de um ou mais filtros de entrada e saída, e opcionalmente, de objectos internos e externos e de declarações de métodos (figura 6). Os filtros são controlados por condições, nomes de filtros, nome das declarações de métodos e nomes das condições que são visíveis para os objectos clientes apesar da sua implementação ser invisível. Na parte de implementação estão as definições de métodos, instâncias de variáveis, definições de condições e opcionalmente operações de inicialização. Esta parte é totalmente encapsulada no objecto, possibilitando através deste, a alteração de mensagens e o seu redireccionamento para objectos internos ou externos. Assim pode ser alcançada a separação de concerns definindo um filtro por cada concern. 26 Figura 6 - Filtros de composição. (adaptada de [Aksit, Wakita et al. 1993]) 2.12 PROGRAMAÇÃO ORIENTADA A ASPECTOS. A origem do termo POA é resumida por Cristina Lopes, “Não me consigo lembrar da data exacta quando decidimos chamar ao nosso trabalho ‘Programação Orientada a Aspectos’, mas lembro-me que o termo foi sugerido por Chris Maeda […]. No meu portátil a primeira referência a ‘POA’ ocorre nos fins de Novembro de 1995. Em Janeiro de 1996, o meu portátil indica que estávamos a usar ‘Open Implementation’ e ‘POA’ ao mesmo tempo, embora para partes diferentes do trabalho do grupo. Em Junho de 1996 enviámos a proposta para o DARPA intitulada ‘Programação Orientada a Aspectos’, no fim de 1996 desaparecem todas as referências a ‘Open Implementation’ do meu portátil.” [Lopes 2002]. 27 Gregor Kiczales como já referido, liderava uma equipa de investigação em “Open implementaions” [Kiczales 1996] e “Metaobject Protocol” [Kiczales, des Rivieres et al. 1991], à qual no verão de 1995 se junta Cristina Lopes, acrescentando conhecimentos de programação adaptativa [Lopes 1996]. Em 1997, Gregor Kiczales apresenta o seu trabalho em “Programação Orientada a Aspectos” na conferência ECOOP’97, expondo os primeiros termos, conceitos e análise dos problemas que se tentavam solucionar [Kiczales, Lamping et al. 1997]. Gregor Kiczales afirma que “[…] O objectivo deste trabalho é tornar possível que os programas consigam claramente capturar todos os aspectos importantes do comportamento do sistema […]” [Kiczales, Lamping et al. 1997]. As linguagens de programação com base num só tipo de abstracção são inadequadas para sistemas complexos. Diferentes tipos de comportamentos do sistema tendem a ter a sua própria “forma”, enquanto um tipo de abstracção pode ser bom para capturar um tipo de comportamento do sistema, é insuficiente num outro tipo de comportamento [Kiczales, Lamping et al. 1997]. As Linguagens de POO conseguem capturar os comportamentos dos objectos mas falham em comportamentos secundários, comportamentos estruturais e comportamentos invariantes. Estes comportamentos não são possíveis de isolar num só local e dispersam-se pelos módulos do sistema, ou seja, são transversais aos módulos. Nesta mistura de comportamentos o resultado é código espalhado que leva à perda de modularidade e a uma possível perda de abstracção. Para combater estas fragilidades a POA apresenta uma nova forma de modularização que permite a implementação de cada aspecto do sistema separadamente na sua forma natural, e a sua composição usando uma ferramenta “Aspect Weaver”. As componentes funcionais continuam a ser implementadas nos paradigmas existentes enquanto as componentes não funcionais são 28 implementadas numa linguagem de aspectos. Criando-se uma solução de abrangência geral e independente do tipo de comportamento do sistema, funcionando como uma extensão. Assim, a POA define Crosscutting concern como um comportamento do sistema que atravessa vários módulos deste. Este é dividido em concerns base, comportamentos primários e crosscutting concern, comportamentos secundários e é adicionada uma nova unidade modular, o aspecto. A implementação de concerns é feita independentemente e a coordenação e comunicação entre eles é relegada para os aspectos. O programador, detentor da compreensão do sistema, fica responsável por explicitamente definir os aspectos, utilizando uma linguagem que deva permitir um nível abstracto adequado e com a localidade apropriada [Kiczales, Lamping et al. 1997]. 2.12.1 AspectJ Uma implementação do paradigma é o AspectJ12 [Kiczales, Hilsdale et al. 2001; Kiczales, Hilsdale et al. 2001] que funciona com uma extensão à linguagem POO Java13, adicionando-lhe conceitos práticos de POA e permitindo a implementação modular de crosscutting concerns. Acrescentando um novo conceito, o join point e alguns construtores: pointcuts, advice, inter-type declarations e aspects. AspectJ foi desenvolvido com a compatibilidade com a linguagem Java em mente, isto é, os programas em Java têm de ser válidos em AspectJ e os programas AspectJ deverão ser válidos na Java virtual machine (JVM) padrão [Lindholm and Yellin 1999]. As ferramentas de suporte 12 13 http://www.eclipse.org/aspectj/ (Acedido, Julho 2008) http://java.sun.com/ (Acedido, Julho 2008) 29 do Java foram alteradas para suportar a linguagem, tais como os Ambientes Integrados de Desenvolvimento (IDEs), bem como, as ferramentas de documentação. Assim, os programadores podem utilizar a extensão AspectJ de forma tão natural como o Java. Como já referido, o AspectJ adiciona: pointcuts e advices que permitem afectar dinamicamente o fluxo normal do programa, inter-type declarations que alteram estaticamente a hierarquia de classes do programa e aspectos que encapsulam todos os novos construtores. Outra adição a enumerar é o conceito de Join Point que define um ponto no fluxo do programa. Estes são utilizados por pointcuts para expor os locais de intercepção onde é necessária a adição de comportamentos transversais. Por sua vez, advice é o bloco de código que vai ser executado quando o join point for alcançado. Na sua conjuntura existem dois tipos de comportamentos possíveis: os comportamentos dinâmicos que avaliam condições definidas nos pointcuts em tempo de execução e os comportamentos estáticos onde as avaliações são feitas em tempo de compilação. Todavia acrescenta-se que os aspectos são as unidades modulares para a declaração dos crosscutting concerns, são similares às classes do Java mas incluem os construtores pointcut, advice e inter-type declaration. Nos próximos subcapítulos expõe-se uma breve introdução da linguagem, tendo como base a guia de programação14. 14 http://www.eclipse.org/aspectj/doc/released/progguide/index.html (Acedido, Julho 2008) 30 Join Point 2.12.1.1 Join Point é um ponto bem definido no fluxo do programa. Este modelo permite capturar: 2.12.1.2 • Chamadas a métodos; • Execução de métodos; • Criação de objectos; • Execução do construtor do objecto; • Inicialização de objectos; • Pré inicialização de objectos; • Referências e atribuição a variáveis; • Tratamento de excepções. PointCuts Tendo por base que os pointcuts permitem seleccionar e agrupar join points, e utilizando a figura 7 como exemplo de referência, pretende-se seleccionar todas as chamadas ao método setX com argumento int e retorno void da classe Point. Figura 7 – Diagrama do exemplo. 31 Para se criar um Pointcut, utiliza-se o operador pointcut e declara-se o seu nome, a sua interface e o join point a capturar. No exemplo 1, expõe-se a utilização da primitiva call (identifica um join point do tipo chamada a método) para a captura de chamadas ao método setX da classe Point. pointcut move(): call(void Point.setX(int)) Exemplo 1 – declaração de um pointcut. De modo a agregar vários join points num só pointcut, aplicam-se os operadores lógicos ‘ou’ definido na linguagem com os símbolos ‘||’, o operador ‘e’ definido na linguagem com os símbolos ‘&&’ e o operador de negação definido na linguagem com o símbolo ‘!’. Para facilitar a atribuição de join points é possível definir o nome do fluxo (método, variável, excepção, etc.) a capturar, utilizando wildcards para os expressar. Denominando este tipo de pointcut, de Property-Based Primitive PointCut, este permite que numa só declaração se agrupem um conjunto de join points, pois estes seguem uma determinada sintaxe no seu nome. No exemplo 2 captura-se todos os métodos da classe Figure cujo nome comece por make com qualquer tipo de parâmetros mas só os métodos declarados com retorno void. call(void Figure.make*(..)) Exemplo 2 - Pointcut utilizando WildCards. 2.12.1.3 Advice Pointcuts seleccionam join points mas não exercem qualquer acção. Porém com o auxílio dos advices possibilita-se implementar o comportamento transversal associando assim, um pointcut a um bloco de código. 32 O bloco de código de um advice que está associado a um ou mais pointcuts pode ser executado em três momentos diferentes: ‘antes’ (exemplo 3), ‘depois’ (exemplo 4) ou ‘ao invés de’ (exemplo 5). before():Pointcut{ Bloco de Código } Exemplo 3 – Advice do tipo ‘antes’. after():Pointcut{ Bloco de Código } Exemplo 4 - Advice do tipo ‘depois’. around():Pointcut{ Bloco de Código } Exemplo 5 - Advice do tipo ‘ao invés de’. 2.12.1.4 Inter-type declarations Inter-Type declarations permitem adicionar comportamentos a classes e alterar as suas hierarquias, declarar membros de classes que se propagam por múltiplas classes ou alterar a relação de herança entre classes. Por exemplo, permite adicionar a uma classe atributos, métodos, ou implementar um padrão de desenho. No exemplo 6 é mostrado como adicionar métodos e variáveis necessárias à implementação do padrão de desenho Observer que posteriormente são compostos pelo Weaver com a classe Point. aspect PointObserving { private Vector Point.observers = new Vector(); public static void addObserver(Point p, Screen s) { p.observers.add(s); } public static void removeObserver(Point p, Screen s) { p.observers.remove(s); } ... } Exemplo 6 – Implementação do padrão de desenho Observer. 33 2.12.1.5 Aspectos Como já mencionado, os aspectos encapsulam os pointcuts, advices e inter-type declarations, para além de poderem conter métodos e atributos. São as unidades modulares onde todo o comportamento relativo a um ou mais comportamentos transversais é implementado e fica localizado (exemplo 7). aspect OmeuAspecto{ //Dentro deste bloco é possível declarar //Métodos e declaração de atributos iguais ao java //pointcuts e advices e inter-type declarations referentes ao AspectJ } Exemplo 7 – declaração de aspectos. 2.13 SEPARAÇÃO MULTIDIMENSIONAL DE CONCERNS Separação multidimensional de concerns é um paradigma para modelar e implementar software artifacts, suportando a separação simultânea de concerns através da decomposição e composição de software artifacts ao longo de múltiplas dimensões, quebrando a “tirania da decomposição dominante”, sendo uma evolução da subject oriented programming [Tarr, Ossher et al. 1999]. Durante o desenho de um sistema são identificados requisitos e funcionalidades que na fase de implementação só são possíveis de decompor de uma forma: por objectos. Funcionalidades que afectem vários objectos vão espalhar-se por diversos outros implicando a evolução e compreensão do sistema. Futuras alterações são intrusivas no sistema ao invés de aditivas. Para alcançar a decomposição por concerns este paradigma utiliza Hyperslices. 34 Relativamente ao hyperslice, tido como um conjunto de módulos convencionais escritos num qualquer formalismo, têm a finalidade de encapsular concerns numa dimensão fora da dimensão dominante. Os módulos desta dimensão contêm todas as unidades que pertencem ou tratam do concern em causa. Sendo bem elaborada a separação de concerns, pode fornecer a muitos engenheiros de software diversos benefícios, entre eles: a complexidade reduzida, a reutilização e a evolução. A escolha dos limites da separação dos concerns depende dos requisitos do sistema e dos tipos de decomposição e composição que um dado formalismo suporta. As metodologias predominantes e formalismos disponíveis só suportam separação de concerns ortogonal, ao longo de uma única dimensão de decomposição e composição [Ossher and Tarr 2000]. Esta tecnologia evolucionou para Concern Manipulation Evironment (CME) [Harrison, Ossher et al. 2005; Harrison, Ossher et al. 2005] suportando a extracção e composição de concerns durante o ciclo de vida de um sistema de software, disponibilizando um conjunto de ferramentas para programadores. CME é assim, um projecto Eclipse15 Open Source, que já não está em desenvolvimento e encontra-se arquivado16. 2.14 DESENVOLVIMENTO DE SOFTWARE ORIENTADO A ASPECTOS Para além de modularizar crosscutting concerns é necessário primeiramente, identificálos numa fase anterior à implementação. A POA tornou-se assim parte de uma área maior, o 15 16 http://www.eclipse.org (Acedido, Julho 2008) http://www.eclipse.org/technology/archived.php (Acedido, Junho 2008) 35 Desenvolvimento de Software Orientado a Aspectos (DSOA), existindo duas grandes áreas nela inserida, a Early-Aspects17 [Rashid, Sawyer et al. 2002] e a POA. A investigação na área Early-Aspects tem como objectivo identificar aspectos crosscutting numa fase inicial da elaboração do sistema, e determinar o impacto dos aspectos identificados em fases posteriores. A Early-Aspects foca-se na engenharia de requisitos. Para atingir um consenso nesta área é introduzido um modelo para a engenharia de requisitos, primeiro são identificados e especificados os concerns seguindo-se, a partir destes, a identificação dos potenciais aspectos que são especificados e atribuídos de uma prioridade. Finalmente é definida a dimensão em que se enquadram os aspectos. Os concerns que influenciem ou interajam com mais de um requisito são chamados aspectos candidatos, prosseguindo-se uma detalhada especificação dos aspectos candidatos, o que permite uma refinação de aspectos mais sucinta e que tipo de interacções e conflitos, entre aspectos existe. Para resolver conflitos entre aspectos é preciso atribuir-lhes prioridades. 2.15 COMUNIDADES E SITUAÇÃO EM PORTUGAL Existe uma associação de DSOA, a nível Mundial18 e Europeu19, que organiza todos os anos uma conferência Internacional20 com Workshops21 sobre esta temática. Em Portugal existe 17 http://www.early-aspects.net/ (Acedido, Julho 2008) http://www.aosd.net/aosa.php (Acedido, Julho 2008) 19 http://www.aosd-europe.net/ (Acedido, Julho 2008) 20 http://aosd.net/ (Acedido, Julho 2008) 18 21 http://portal.acm.org/browse_dl.cfm?linked=1&part=series&idx=SERIES10702&coll=portal&dl=ACM&CFID=948 5317&CFTOKEN=42857112 (Acedido, Julho 2008) 36 um grupo de pesquisa na área de early-aspects22, sediada na Universidade Nova de Lisboa sob a liderança da Professora Doutora Ana Moreira. 2.16 DESAFIOS, PROBLEMAS E OPORTUNIDADES A POA teve uma fase de evolução e amadurecimento desde que foi apresentada em 1997 até 2002, ano em que foi incorporada como uma subárea da DSOA. O desenvolvimento da investigação académica está na sua maioria focado na subárea early-aspects, desenvolvendo-se ferramentas para a identificação de aspectos na fase de desenho e como estes se deverão relacionar com o resto do sistema. Deste modo, estão a ser elaboradas ferramentas, que permitem a criação visual de aspectos de uma forma similar com o que é feito com UML para a POO. Na fase de implementação estão a ser elaboradas ferramentas que permitem visualizar a interacção dos aspectos com o resto do sistema e efectuar a depuração do código, aproveitando também o novo conceito de modularização através de bytcode weaving para criar sistemas mais modulares, onde existe adição ou remoção, em tempo de execução, de funcionalidades e requisitos consoante as necessidades. Alguns paradigmas apresentados posteriormente evoluíram e agora fazem parte integrante da POA, tais como o Adpatative programing [Lieberherr, Orleans et al. 2001]. Como tecnologia emergente, a POA tem a potencialidade de facilitar e diminuir o tempo de desenvolvimento de um sistema de software. Para o sucesso da tecnologia é necessário uma 22 http://aosd.di.fct.unl.pt/aosd-group/ (Acedido, Julho 2008) 37 maior adaptação ao nível comercial. Para tal, é essencial uma melhor integração com as tecnologias existentes, para que assim exista uma transição natural sentida como uma evolução. Esta evolução não pode ser só das linguagens de programação existentes, tem de envolver todas as ferramentas que são utilizadas com essa linguagem, entre outros, editores, documentação e depuradores. A constante transformação do mundo actual determina que o que é uma necessidade hoje, pode ser substituída por uma outra necessidade completamente diferente amanhã. O que influencia as empresas a estar em constante evolução e alterarem as suas regras de negócio. Toda esta conjuntura de factos em constante mudança, evidência a necessidade de uma nova forma de se conseguir desenvolver software que permita em tempo útil, acompanhar estas mudanças. A POA possibilita este facto através da adição de funcionalidades e regras de negócio, que não são intrusivas à evolução e/ou modificação dos sistemas. Devido à nova forma de modularidade é fundamental a existência de alguma coordenação das alterações que se possam efectuar no sistema, para que o seu comportamento seja previsível, ou seja, as alterações não devem conduzir a aspectos que deixam de interceptar pontos no fluxo ou estes fluxos deixem de ser o ponto de intercepção pretendido, tornando o comportamento do sistema imprevisível. 38 39 3. PROGRAMAÇÃO ORIENTADA A ASPECTOS Nos pontos que se seguem tentar-se-á abordar os problemas inerentes à POA, bem como, as suas primordiais vantagens e desvantagem que influenciaram a busca de soluções por parte da POA. Assim, incidir-se-á numa primeira análise do que se entende por um sistema POA, seguindo-se a exposição das tecnologias existentes, aplicações e estudos quantitativos inerentes a esta área de investigação. 3.1 COMPORTAMENTOS GLOBAIS A motivação do paradigma da Programação Orientado a Aspectos, é ajudar a modularizar múltiplos aspectos de um sistema de software, aplicando-lhe o princípio da separação de concerns. O sistema é composto por módulos independentes, raciocinando modularmente e sem as dificuldades e complexidades apresentadas por outras tecnologias de separação de concerns semelhantes. A partir da figura 8 mostra-se dois módulos da mesma aplicação, cada um dos módulos implementa uma determinada funcionalidade necessária pela aplicação, contudo existe ainda uma dependência explícita de entre os dois módulos. O módulo A necessita de conter referências ao módulo B, nas várias unidades que o compõem, deste modo os dois módulos não são totalmente independentes. 40 Figura 8 - Interacção entre Módulos. Na figura 9 pode verificar-se a existência de uma sobreposição de uma unidade elementar B1 pertencente ao módulo B a três unidades do módulo A. Esta dependência é em certo modo, criada pela forma como se estruturou o programa e por condicionantes introduzidas pelo paradigma que se está a utilizar na implementação, por exemplo, a hierarquia de classes no paradigma orientado a objectos. Esta dependência entre módulos vai condicionar a sua independência e reutilização para futuros projectos. Figura 9 - Sobreposição de unidades elementares. Existem comportamentos num sistema de software que se repetem sistematicamente em vários módulos do sistema, não sendo possíveis de se situarem num só local pois são uma 41 necessidade global do sistema, criada pela necessidade do sistema em ter um certo comportamento específico (figura 10). Figura 10 - Comportamentos Globais do Sistema 3.2 ANÁLISE DO PROBLEMA O primeiro problema que se pode identificar diz respeito ao desenho da arquitectura de um sistema de software. O engenheiro de software, quando lhe é atribuída a tarefa de criar um novo sistema de software, depara-se com uma série de dilemas que afectam a criação do sistema, nomeadamente o tempo de que se dispõe para criar o sistema e a evolução dos requisitos do sistema. Não existem certezas de que os requisitos não vão sendo alterados durante o desenvolvimento do sistema ou sua posteriori. 42 Dependendo dos factores anteriores criam-se sistemas que são mais rápidos de produzir, ou seja, têm um tempo reduzido para sair para o mercado, mas são difíceis de evoluir posteriormente e nem sempre apresentam garantias de qualidade. Por outro lado uma arquitectura do sistema mais cuidada permite uma melhor evolução do sistema, com maior qualidade, mas implicando maior tempo de desenvolvimento e gastos adicionais. Definir um meio-termo não é fácil, mas focando-se apenas nos requisitos presentes e desenvolvendo um sistema com base em tecnologias que permitam a acomodação modular de novos requisitos, ou as suas alterações, obtém-se assim uma solução que permitirá a elaboração de um sistema de melhor qualidade, em menos tempo e com menor custos. A tarefa de gerir projectos é contudo um factor crítico, devido à falta de modularidade implicando deste modo mais comunicação entre equipas, reuniões e debates o que encurta o tempo de desenvolvimento. Não obstante, outro problema inerente é o facto de que a tecnologia utilizada para a implementação produz um impacto negativo na modularização, ou seja, muitas vezes aquando do desenho do sistema para a fase de implementação perde-se modularidade. A tecnologia usada na implementação não permite implementar modularmente todas as preocupações desenhadas e identificadas e algumas acabam por se sobrepor a outras (exemplo 8). 43 public class SomeBusinessClass extends OtherBusinessClass { "Dados do módulo" "Stream de Registo" "Flag de consistência de dados" (1) "Métodos redefinidos da super classe" public void performSomeOperation(<informação da Operação>) { "Garantir autenticidade" "Garantir que a informação satisfaça contrato" "Bloquear o objecto para garantir consistência" (2) "Garantir que a cache está actualizada" "Fazer o registo do início da operação" "Realizar Operação" "Fazer o registo do final da operação" (3) "Desbloquear o objecto" } public void save(<persistência de dados>) { "..." } (4) public void load(<persistência de dados>) { "..." } } Exemplo 8 – Intrusão em Classes. 44 No exemplo 8 as secções 1, 2, 3 e 4 não fazem parte da lógica primária da classe, são preocupações referentes a funcionalidades globais do sistema existindo uma invasão de código na classe, de funcionalidades que não contribuem para a implementação do seu objectivo primário. Este problema de modularização resulta dos comportamentos que são transversais aos objectos, são globais ao sistema e produzem espalhamento (scaterring) e intrusão (tangling) de código [Kiczales, Lamping et al. 1997]. Sendo o espalhamento um efeito causado por um comportamento transversal, que adiciona código extra em vários módulos do sistema de software, para implementar um determinado comportamento. E intrusão o efeito de ter vários comportamentos transversais na mesma secção de código, por exemplo num método, o que gera o denominado código esparguete. A falta de modularização no código produz efeitos negativos no desenvolvimento e evolução dos sistemas de software, tais como [Laddad 2003]: • Baixa produtividade: o desenvolvimento de um módulo deixa de ser exclusivamente incidente numa só área específica para se alargar a outras. O programador tem de possuir conhecimento de todas essas áreas, aumentando assim o tempo que demora a desenvolvê-las. As revisões feitas no módulo têm de ser efectuadas com todos os especialistas das áreas abrangidas e quanto maior o grupo de pessoas, mais difícil se torna estabelecer um horário que permita a sua reunião; • Baixa qualidade: o aumento do código necessário para implementar os módulos, devido ao aumento das áreas de foco de cada programador, aumenta a probabilidade da existência de erros e de secções elaboradas com menor rigor; 45 • Baixa reutilização: as alterações feitas nos módulos, para introduzir os comportamentos transversais, são invasivas para a reutilização do módulo noutro projecto. Ter-se-á assim, de remover todos esses comportamentos adicionais das classes; • Baixa evolução: a adição ou alteração de comportamentos poderá levar à reorganização ou modificação de vários módulos; • Difícil compreensão: a leitura e compreensão de um módulo implica possuir um conhecimento global de todas as áreas que este implementa, ou seja, o sistema tem de ser compreendido na sua globalidade; • Problemas de depuração: a depuração tem de ter em conta todas as áreas que o módulo implementa. Neste contexto, um exemplo de intrusão são os padrões de desenho. Embora estes melhorem a modularidade dos sistemas, algumas classes necessitam de ser modificadas para acomodarem as alterações. Com a POA os padrões de desenho são desenvolvidos separadamente e as alterações necessárias, nas classes, são implementados em aspectos, sendo posteriormente compostos com as classes (secção 3.15). 46 3.3 SISTEMA MODULAR A POA visa permitir modularizar, na sua forma natural, software concerns que não são adequadamente decompostos pelos paradigmas mais comuns, em especial o paradigma orientado a objectos. Como já exposto, um concern é uma funcionalidade ou requisito do sistema de software, e pode variar desde noções de alto nível como segurança e qualidade de serviço, até noções de baixo nível como caching e buffering. Os concerns podem ser funcionais ou não funcionais [Elrad, Filman et al. 2001]. Um módulo pode ser definido de várias formas, embora seja geralmente um componente de um sistema de software que opera independentemente de outros módulos. Assim, a modularização de todos os concerns de um sistema de software permite uma nova forma de raciocínio, de análise e implementação do sistema por módulos. Contudo o código de um concern torna-se modular se for possível [Kiczales and Mezini 2005]: • Ser localizado textualmente; • Existir uma interface de interacção bem definida com o resto do sistema; • A interface ser uma abstracção da implementação, sendo possível alterar a implementação sem violar a interface; • O módulo ser automaticamente composto, por um compilador (Weaver), loader, linker, em várias configurações, com outros módulos para produzir um sistema. Raciocinar modularmente permite tomar decisões em relação aos módulos focando-se apenas e só na sua implementação. É necessário analisar previamente o sistema na sua 47 globalidade e a POA assim o exige de maneira a permitir, depois de completa a análise global, o raciocínio modular. Desta forma, a POA permite estruturar o desenho e o código do sistema de acordo com o que os programadores pensam do sistema, possibilitando assim a decomposição e composição de software concerns, em especial a modularização de crosscutting concerns [Elrad, Aksit et al. 2001]. Crosscutting concerns são aspectos de um sistema de software que afectam outros concerns, ou seja, são concerns que são transversais a outros concerns [Kiczales and Mezini 2005]. A POA permite a implementação modular de crosscutting concerns, junto com os benefícios prévios da POO, funcionando como uma extensão do paradigma POO. A base do sistema de software contínua a ser implementada usando POO. Crosscutting concerns são igualmente implementados separadamente e encapsulados em unidades modulares chamadas aspectos. Com a ajuda de ferramentas os módulos crosscutting são combinados (Weaving) com os outros módulos base do sistema para gerar o programa final. 3.4 SISTEMA POA Para alguns autores, a POA pode ser entendida como o desejo de se fazer declarações quantificadas sobre o comportamento de programas, e conseguir com que essa quantificação estruture programas escritos por programadores alheios a essa quantificação [Filman and Friedman 2000]. 48 Segundo a opinião dos autores Filman and Friedman existem duas propriedades necessárias para uma linguagem de programação ser considerada POA a ‘quantificação’ e a ‘alheabilidade’ [Filman and Friedman 2000]. A quantificação é uma atribuição na forma no programa P, quando se disputar qualquer condição C, efectua-se a acção A em P. Definindo as seguintes preocupações para o programador e/ou engenheiro de software: • Quantificação: que tipo de condições C se podem especificar? • Interface: como é que as acções A interagem com o programa base e entre eles? • Entrelaçamento: como irá o sistema interligar a execução das acções base do programa P com as acções A? A quantificação pode ser estática, quantificação sobre programas na forma de texto, ou dinâmica que associa aspectos a eventos que acontecem durante a execução do programa. Quanto à ‘alheabilidade’, esta estipula que ao examinar o bloco de código de um módulo base, não se deve conseguir afirmar se o código de um aspecto irá ser executado. Não deve existir acomodação do módulo base. Em suma, um sistema POA será um sistema que permite quantificar acções e através de uma interface interagir com o programa base. A combinação das várias componentes é feita através da forma de texto ou de eventos, sendo o sistema base alheio a todo este processo. 49 3.5 METODOLOGIA DSOA O desenvolvimento de um software orientado a aspectos é geralmente feito em três etapas: 1. Decomposição aspectual: são identificados os requisitos do sistema, apontando os requisitos funcionais, denominados base concerns, e requisitos não funcionais, os crosscutting concerns; 2. Implementação de concerns: procede-se à implementação independente dos vários concerns. A implementação de cada concern é feita separadamente recorrendo a um paradigma de programação, sendo o paradigma mais utilizado a POO; 3. Recomposição aspectual: definem-se as regras de composição dos vários módulos do sistema previamente implementados. Estas regras são implementadas em unidades modulares chamadas aspectos. O processo de composição Weaver utiliza-as em conjunto com os módulos para produzir o sistema final. Estas regras são definidas numa linguagem de programação que implementa o paradigma orientado a aspectos. A partir da figura 11 é possível visualizar a interacção das várias etapas. 50 Figura 11 - Metodologia POA. (Adaptada de [Laddad 2003]) 3.6 ANATOMIA DA LINGUAGEM POA O Paradigma Orientado a Aspectos permite especificar, separadamente, como um módulo deve reagir a um evento que ocorre em outro módulo. Esta ideia permite a separação dos módulos já que estes não interagem directamente. Uma linguagem que implemente o paradigma tem de possuir: • Uma especificação que descreva os construtores e a sintaxe que irá ser utilizada para combinar os crosscutting concerns com a lógica dos base concerns; • Verificação da aderência do código à especificação da linguagem e gerar o programa, o que é geralmente feito por um compilador ou interpretador. 51 3.7 ESPECIFICAÇÃO DAS LINGUAGENS Uma implementação do Paradigma Orientado a Aspectos tem de possuir uma linguagem que permita a implementação dos software concerns e uma linguagem que permita definir regras de combinação dos vários softwares concerns. 3.7.1 Software concerns Para a implementação de software concerns em módulos são normalmente utilizadas linguagens orientadas a objectos tais como: Java, C++ e C#. 3.7.2 Regras de combinação As regras de combinação definem como interligar os software concerns previamente implementados, de forma a criar o sistema final. Alguns aspectos da combinação de concerns a levar em conta na especificação da linguagem são: • Correspondência da linguagem: modo com o qual se descreve quais entidades serão compostas entre si. A correspondência pode ser implícita (determinada por regras da linguagem) ou explícita (descrita pelo programador); • Semântica composicional: descreve o que deve acontecer com os elementos que correspondem. A linguagem pode definir diversas semânticas que em geral são escolhidas pelo programador; 52 • Tempo de ligação: diz respeito ao momento em que a correspondência tem início. Pode ser estático (em tempo de compilação) ou dinâmico (em tempo de execução). 3.8 COMPOSIÇÃO DE SISTEMAS A implementação da linguagem permite combinar os software concerns, utilizando as regras de combinação e converter o resultado da combinação em código executável. O processo de combinação é chamado weaving e o executante, compilador deste processo, é denominado Weaver. 3.8.1 Weaving É o processo de composição do sistema de software concerns com aspectos. As regras de composição estão definidas nas unidades modulares, aspectos. Esta separação faculta que as alterações das regras permitam gerar novas formas de combinação e assim novos sistemas de software sem alteração dos software concerns. As regras lógicas possibilitam à linguagem a geração de eventos virtuais e responder com uma acção. Esta acção é associada a um crosscutting concern. 53 3.8.2 Weaver Weaver é o processo que efectua a combinação dos concerns. Dependendo da implementação do Weaver este pode permitir a combinação de código fonte, a combinação de código intermédio [Hilsdale and Hugunin 2004] (bytecode23), ou estar incluído na própria maquina virtual. Por exemplo, a JVM24 ao executar os programas em código intermédio, permite despontar eventos próprios para a introdução de comportamentos adicionais. Na combinação do código fonte (figura 12), o dos módulos é entrosado pelo Weaver com o dos aspectos, na linguagem de POA. O resultado é posteriormente fornecido ao compilador da linguagem de programação base. Neste método não é possível combinar bibliotecas que estejam pré-compiladas, já que não é possível ter acesso ao seu código fonte. Figura 12 – Weaver de código fonte. Na combinação de código intermédio (figura 13), todos os concerns são previamente précompilados pelo compilador do paradigma base, para código intermédio e os aspectos são pré- 23 24 http://www-128.ibm.com/developerworks/ibm/library/it-haggar_bytecode/ (Acedido em Julho 2008) http://dev2dev.bea.com/pub/a/2005/08/jvm_aop_2.html?page=1 (Acedido em Julho 2008) 54 processados pelo compilador de aspectos e combinados com o código intermédio dos software concerns, para gerar o código executável ou código intermédio final. Neste método já é possível importar bibliotecas pré-compiladas. Figura 13 - Weaver de código intermédio. 3.8.2.1 Proxy Dinâmica O processo de weaving em alguns Weavers é baseado em Proxies dinâmicos25. Um Proxy dinâmico encapsula um objecto. Invocações em métodos desse objecto são efectuadas no proxy que posteriormente invoca os métodos no objecto. Isto permite que o proxy permita executar tarefas (advices), ‘antes’, ‘depois’ ou ao ‘invés de’. Numa situação normal, a classe do objecto é alheia a qualquer alteração não sendo executada nesta classe qualquer tipo de alteração (exemplo 9). 25 http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Proxy.html (Acedido em Julho 2008) 55 public class SimplePojo implements Pojo { public void foo() { //invocação directa this.bar(); } public void bar() { //logica } } Exemplo 9- classe a ser interceptada. Do ponto de vista do objecto a comunicação entre objectos é directa (figura 14). Figura 14 - Comunicação normal entre objectos. Normalmente para se invocar um método num objecto instancia-se o objecto e invoca-se o método directamente (exemplo 10). public class Main { public static void main(String[] args) { Pojo pojo = new SimplePojo(); // invocação directa na referencia do objecto pojo.foo(); } } Exemplo 10 – Invocação de métodos na classe normalmente. Para adicionar um Proxy é necessário alterar o código que instancia o objecto e adicionar toda a lógica referente ao proxy. Estas alterações são efectuadas automaticamente pelas 56 ferramentas de compilação POA. No exemplo 11 é possível verificar o tipo de alterações necessárias para invocar um método num objecto. O código da classe ProxyFactory não é exemplificado. public class Main { public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(new SimplePojo()); factory.addInterface(Pojo.class); factory.addAdvice(new RetryAdvice()); Pojo pojo = (Pojo) factory.getProxy(); // chamada ao método mas no proxy! pojo.foo(); } } Exemplo 11 - Invocação de métodos utilizando uma proxy. Ao ser inserido o padrão de desenho Proxy, o programa primeiro invoca o método no proxy e este posteriormente invoca o método no objecto (figura 15). Figura 15 - Comunicação utilizando uma proxy. Desta forma é possível adicionar comportamentos adicionais (figura 16). 57 Código da Invocação Pojo.foo() Proxy 1) Foo() na Proxy Advices 2) Execução de Advices Advice Objecto 3) Foo() no Objecto Figura 16 – Adição de comportamentos adicionais através de proxys. Esta solução acrescenta lógica adicional. Assim, a execução do programa vai ter mais instruções o que vai afectar a performance do programa, para além de que só permite a captura de fluxo dinâmicos. 3.9 BENEFÍCIOS DA POA Como benefícios da utilização do Paradigma Orientado a Aspectos pode-se enumerar: • Código mais limpo: a separação modular de todos os softwares concerns permite efectuar a análise e leitura do código escrito módulo a módulo. Assim, ao ser 58 necessário, só ter em consideração uma área de cada vez é mais fácil de se perceber, editar e estender o código escrito; • Modularização superior: para além das formas normais de modularização da linguagem base é possível modularizar e localizar textualmente, segundo a forma natural os crosscutting concerns; • Fácil de evoluir: a nova forma de modularização em conjunto com a definição de regras de composição permite criar novos sistemas, com o mínimo ou mesmo nenhumas alterações dos módulos de software. A adição de módulos ou redefinição de regras para alterar, editar, ou melhorar um sistema de software é simplificado e as alterações facilmente localizáveis; • Facilidade de adicionar requisitos de última hora: a separação de software concerns permite a alteração de código fonte, com o mínimo de repercussões ao nível da estrutura do sistema de software. Modificações, como por exemplo, para melhorar a performance ou adicionar/alterar regras de negócio, são facilmente acopladas ao sistema; • Mais código reutilizável: a eliminação do efeito de intrusão e espalhamento permite que os módulos sejam mais facilmente acoplados a outros sistemas de software; • Rápido de implementar: depois da análise e da definição de interfaces é mais fácil dividir por equipas e implementar os módulos paralelamente, não sendo necessárias reuniões para alterações de necessidades locais; 59 • Redução de custos: a possibilidade de desenvolver o sistema mais rapidamente, distribuir tarefas paralelas e aumentar a reutilização tem como efeito a redução dos custos. 3.10 DESENVOLVIMENTO DE SOFTWARE ORIENTADO A ASPECTOS Um paradigma de programação por si só não é suficiente para a elaboração de um sistema, é preciso possuir ferramentas e tecnologias que facilitem o desenho e posteriormente o desenvolvimento do sistema. Assim, a POA evoluiu para o Desenvolvimento de Software Orientado a Aspectos26 (figura17). Figura 17 - Sistema de Desenvolvimento de Software Orientado a Aspectos. 26 http://www.aosd.net/ (Acedido em Junho 2008) 60 3.10.1 Identificação de concerns Nesta etapa é feita a identificação e separação de software concerns que permitirá a separação de tarefas e reduzir a complexidade do desenho e da implementação. Nesta área em particular surge o termo Early-aspects27 que refere a identificação de aspectos ao nível da arquitectura do sistema (figura 18). A investigação nesta área prende-se com o facto de que é necessário fazer uma identificação realista dos interesses do sistema e como os aspectos vão afectar o sistema, bem como, a sua evolução futura. Figura 18 – Incisão do estudo de Early-Aspects. 3.10.2 Implementação A subárea encarregue da implementação dos sistemas é a POA. Para tal, existe uma ampla panóplia de linguagens de programação que estende as linguagens mais populares como 27 http://www.early-aspects.net/ (Acedido em Junho 2008) 61 Java, C/C++, SmallTalk. Tornando-se a linguagem padrão e uma das mais populares dentro da comunidade, o AspectJ estende da POO Java. 3.11 DEVENVOLVIMENTO UTILIZANDO ASPECTJ AspectJ [Kiczales and Hilsdale 2001] é uma extensão à linguagem de programação Java e implementa conceitos práticos do paradigma orientado a aspectos. Tem suporte para dois tipos de implementações de crosscutting concerns: • Dinâmicos: permitem acrescentar comportamentos adicionais em pontos bem definidos no programa; • Estáticos: permitem alterar a estrutura de classes e/ou interfaces. A linguagem foi desenvolvida pelo grupo de Gregor Kiczales e actualmente o desenvolvimento da linguagem está a cargo do projecto Eclipse, que disponibiliza no website da plataforma, ferramentas de ajuda ao desenvolvimento em POA. Benefícios da linguagem AspectJ: • Generalista; • Fácil de aprender; • Fácil de associar; • Adopção por fases. 62 3.11.1 Análise A análise à linguagem AspectJ, que descreve os vários tipos de PointCut, tipos de Advices, inter-type-Declarations e exemplos de utilização, pode verificar-se no anexo 1. 3.11.2 @AspectJ Na versão 1.5 da linguagem de programação AspectJ foi introduzido um novo estilo de declarar aspectos, advices e pointcuts, mas mantendo contudo a mesma semântica (anexo 1). A este estilo de desenvolvimento foi chamado @aspectJ28, sendo semelhante às anotações do Java 5 e idêntico à aplicação de atributos da Framework .NET. Este estilo permite desenvolver aspectos como se de classes normais de Java se tratassem, posteriormente anotadas, utilizando a semântica do AspectJ. Os métodos dessas classes quando anotados tornam-se Advices. Assim, torna-se possível desenvolver programas de POA sem alterar as ferramentas de desenvolvimento, contudo é necessário incorporar manualmente o método de Weaving no processo de compilação do projecto, através por exemplo, de tarefas ANT29. Se não existir integração do processo de compilação o projecto/programa compilado tem o comportamento normal POO. 28 29 http://www.eclipse.org/aspectj/doc/released/adk15notebook/ataspectj.html (Acedido em Junho de 2008) http://ant.apache.org/resources.html (Acedido em Junho 2008) 63 Neste âmbito, antes de se poder desenvolver programas POA utilizando este estilo, é necessário instalar primeiro a linguagem AspectJ30 e importar as bibliotecas, ficheiros jar, ao projecto Java. A análise a este estilo de programação POA encontra-se no anexo 2. 3.11.3 Ferramentas de Desenvolvimento Para facilitar e complementar a utilização das tecnologias POA surgiram e/ou foram adaptadas ferramentas que auxiliam os programadores. Estas encontram-se enumeradas no anexo 3. 3.12 POSTSHARP PostSharp é uma tecnologia POA para a Framework .NET da Microsoft. AspectJ é só utilizável em projectos Java, não existindo soluções para outras populares linguagens e tecnologias. É ao encontro desta necessidade para a Framework .NET que o PostSharp foi criado. Postsharp31 é uma plataforma de código aberto, que permite transformar e analisar assemblies da tecnologia .NET da Microsoft. Uma assembly é um conjunto de módulos, 30 31 http://www.eclipse.org/aspectj/downloads.php (Acedido em Junho 2008) http://www.postsharp.org/ (Acedido, Julho 2008) 64 unidades unitárias, e com um cabeçalho que indica os módulos que compõem a assembly. Cada módulo é armazenado no formato portable executable (PE), código intermédio, MSIL. Postsharp Laos (Lightweight Aspect-Oriented System), é por sua vez, um weaver que permite criar atributos para a framework .Net como se de aspectos se tratassem, sendo aplicados a classes, métodos ou variáveis. Os atributos são metadados que descrevem, ou anotam, elementos específicos tais como classes, métodos ou propriedades. Os metadados são adicionados à assembly e podem ser acedidos através da API Reflection da framework .NET A tecnologia consiste, deste modo, num conjunto de assemblies que se registam na Framework .NET e um post-compiler para compor o sistema, o qual se integra no processo de compilação do Visual Studio, MSBuild. Postsharp Laos é um plug-in (figura 19) de uma plataforma maior: o Postsharp Core. Esta plataforma é uma infra-estrutura genérica, para a análise e transformação de programas, como AOP weavers, análise estática e optimizadores, podendo ser utilizada por investigadores e/ou programadores para se abstraírem dos problemas da infra-estrutura e se focarem na sua área de investigação. Figura 19 – Processo de integração do Postsharp. (Adaptada do ficheiro de ajuda do Postsharp) 65 3.12.1 PostSharp LAOS O plug-in Postharp LAOS [Fraiteur 2008] permite de forma simples implementar funcionalidades práticas do Paradigma Orientado a Aspectos num projecto. NET Framework. A nomenclatura utilizada no Postsharp Laos foi adaptada para ser facilmente interpretada pela comunidade da Framework. NET, facilitando a sua utilização aos programadores menos familiarizados com os conceitos da POA. Na tabela 4 apresenta-se uma comparação entre os termos técnicos utilizados na linguagem de programação AspectJ e os utilizados no PostSharp Laos. AspectJ Join Point Pointcut Advice Aspecto PostSharp Laos Meta-evento Atribuição de um atributo a um tipo (classe, método ou propriedade). Meta-Handler Conjunto coerente de advices aplicados a um evento de um elemento alvo predefinido, geralmente encapsulados num atributo. Tabela 2 - comparação da nomenclatura do AspectJ e Postsharp Laos. Neste contexto, o PostSharp Laos disponibiliza classes que são consideradas aspectos e os métodos destas podem ser vistos como advices. O programador estende as classes e redefine os métodos de acordo com o comportamento pretendido. Como estas classes derivam da classe System.Atribute todas as classes que derivem destas são atributos, e ao serem aplicadas a um tipo, classe, método ou propriedade, definem um Pointcut. Do ponto de vista do programador do módulo base um aspecto não é mais que um atributo .NET. 66 É disponibilizado pela Plug-in um Namespace32, Postsharp.Laos, que contem um conjunto de classes que permitem implementar os atributos de acordo com o tipo de comportamento pretendido. Sendo assim, é possível capturar chamadas a métodos, declarar excepções, capturar acesso a variáveis e implementar métodos. Os atributos são aplicados a tipos. Um tipo pode ser um método, uma classe ou uma estrutura. Se um atributo for aplicado a uma classe e se este é do tipo advice para capturar chamadas a métodos, então fica aplicado a todos os métodos da classe caso não seja definido outro tipo de comportamento nas definições de atribuição do atributo. 3.12.1.1 Características do PostSharp Laos • Aspectos reutilizáveis: os atributos são classes e como tal são reutilizáveis entre projectos; • Aspectos não invasivos: não se quebra o encapsulamento, os aspectos são aplicados sem alterar o bloco de código a que estão aplicados. O PostSharp Laos envolve o bloco de código como uma caixa dentro de outra caixa; • Aspectos parametrizáveis: podem ser adicionados parâmetros que são correctamente instanciados na fase de inicialização e posteriormente serializados para a assembly. 32 http://msdn.microsoft.com/en-us/library/ms973231.aspx (Acedido em Julho 2008) 67 3.12.1.2 Limitações do PostSharp Laos • Performance: o PostSharp Laos sofre das mesmas limitações dos weavers com base em proxys dinâmicos (capitulo 3.8.2.1), influenciando negativamente na performance dos programas; • Portabilidade: correntemente o PostSharp só é suportado para a plataforma. Net não o sendo nas plataformas. NET Compact Framework e Mono; • Expressividade limitada: embora existam várias maneiras de expressar pointcuts, estas comparadas com outras ferramentas POA, mostram que PostSharp Laos é bastante limitado; • Conjunto de advices limitados: PostSharp Laos embora possibilite um conjunto prático de advices, não oferece alguns tipos úteis, como por exemplo a introdução de variáveis. 3.12.2 Análise da Tecnologia No anexo 4 é descrita a forma de utilização das classes que o PostSharp Laos disponibiliza e um pequeno exemplo de utilização. 3.12.2.1 Ciclo de vida de um Aspecto Postsharp LAOS Desde a compilação até à execução, os aspectos desenvolvidos em PostSharp LAOS passam por várias fases: 1. Instanciação: nesta fase o weaver do PostSharp Laos instancia cada aspecto que está aplicado a um tipo; 68 2. Validação: verifica que a atribuição dos aspectos é feita correctamente, por exemplo, não permitir aplicar aspectos de chamada de métodos a variáveis. Esta validação é feita, em primeira instância, pelo weaver e numa segunda fase através da invocação de um método, que pode ser implementado em cada classe disponibilizada pelos PostSharp Laos, denominado de CompileTimeValidate, permitindo ao programador estabelecer regras (exemplo 12); public override bool CompileTimeValidate(object element) { //implementação das validações } Exemplo 12 - Método CompileTimeValidate. 3. Inicialização: consiste na invocação do método CompileTimeInitialize presente em cada classe disponibilizada pelo PostSharp Laos (exemplo 13) e que permite efectuar alguns tipos de cálculos com os dados compilados até esta fase, para assim não os efectuar em tempo de execução; public override void CompileTimeInitialize(object element) { //implementação //atribuir variaveis com resultados pre calculados em compile time } Exemplo 13 – Método CompileTimeInitialize. 4. Serialização: todas as instâncias dos aspectos são serializadas como metadados na assembly; 69 5. Uso em tempo de execução: os atributos são lidos, serializados e utilizados no programa; 3.12.3 PostSharp Core PostSharp Core é a base em que os plugins como PostSharp Laos são desenvolvidos, trabalhando ao mais baixo nível. O PostSharp Core compõe-se por: • Um modelo de objectos em bytecode, que permite ao PostSharp ler e escrever assemblies .NET framework; • Infra-estrutura da plataforma, onde todas as tarefas a desempenhar pelos plugins ficam definidas em ficheiros XML; • Weaver de baixo nível, que consiste num weaver que injecta instruções em assemblies e é a base para outros weavers de alto nível, como o do PostSharp Laos. 3.12.4 AspectJ vs. Postsharp LAOS Enquanto o AspectJ é uma extensão da linguagem de programação, por sua vez, o PostSharp é um componente que se adiciona à Framework .NET. Para tal é necessário ter uma compreensão extensiva da tecnologia para desenvolver plugins. Utilizar esses mesmos plugins é uma tarefa simples para programadores habituais da plataforma. De seguida são apresentadas as principais diferenças entre o AspectJ e o PostSharp Laos: 70 • O AspectJ não possui restrições de licenças enquanto o PostSharp usufrui de vários tipos de licenças, dependendo porém da biblioteca em questão33; • O AspectJ é uma extensão à linguagem de programação java. O PostSharp Laos adiciona à framework .NET namespaces e um compilador adicional; • O AspectJ permite operadores lógicos em pointcuts (‘&&’, ‘||’, ‘!’). O PostSharp Laos permite a composição de aspectos de forma mais limitada; • O AspectJ tem definições próprias para a implementação de aspectos. No PostSharp Laos a definição de aspectos é através de atributos para a plataforma .Net; • Em AspectJ os advices podem ser executados antes, depois e invés de. PostSharp Laos só permite este tipo de comportamento em certos aspectos; • 3.12.4.1 AspectJ e PostSharp integram-se transparentemente nas respectivas tecnologias. Definição de Join Points O PostSharp permite criar atributos personalizados, sendo o local onde estes são aplicados definidos pelo programador, mas este está restringido às definições permitidas pelo plugin. Caso se esteja a utilizar a biblioteca Core, é possível definir qualquer tipo de Join Points. No AspectJ existe vários tipos de Join Points. Nas tabelas 7, 8 e 9 pode-se observar quais são as soluções apresentadas pelas duas tecnologias para os vários tipos de comportamentos. 33 http://www.postsharp.org/about/license/ (Acedido em Setembro de 2008) 71 PointCut AOP Invocação Inicialização Acesso AspectJ call/execution get / set Postsharp Laos OnMethodBoundar yAspect/ OnMethodInvocati onAspect Staticinitialization/ initialization N/A OnFieldAcces sAspect Tratamento de Excepções handler OnExceptionAs pect Tabela 3 – Comparação de pointcuts entre PostSharp Laos e AspectJ PointCut (continuação) AOP AspectJ Controlo de fluxo Cflow/ cflowbelow Containment Within/ withincode Condições if Postsharp Laos N/A N/A N/A contexto this/ target/ args Parametros Tabela 4 - Comparação de pointcuts entre PostSharp Laos e AspectJ Advices AOP AspectJ Postsharp Laos Advice Antes, Depois, ao invés de Antes, Depois, ao invés de (depende do tipo de pointcut) Tabela 5 – comparação de tipos 3.12.4.2 Definição de Aspectos Em AspectJ, os aspectos são desenvolvidos através de código Java encapsulando tanto o código normal do advice como a definição de pointcuts (exemplo 14). 72 public class HelloWorld { public void say ( String msg ) { System.out.println ( msg ); } public static void main ( String[ ] args ) { new HelloWorld ( ).say ( "Hello, world! " ); } } public aspect HelloWorldAspect { pointcut sayPoint ( ): execution (void HelloWorld.say ( String )); after( ): sayPoint( ) { System.out.println( "Over!" ); } } Exemplo 14 – exemplos de um aspecto em AspectJ. Por sua vez, em PostSharp Laos, os aspectos são desenvolvidos como atributos .NET (exemplo 15), ficando isolados nestes, tendo a sua atribuição que ser declarada fora do atributo e no tipo ao qual se pretende capturar o comportamento (exemplo 16). using PostSharp.Laos; … namespace TraceSample { [Serializable] class TraceAttribute: OnMethodBoundaryAspect { public override void OnExit(MethodExecutionEventArgs eventArgs) { Console.WriteLine("On exit of {0}.", this.methodName); } } } Exemplo 15 – Exemplo de um atributo com base em classes do PostSharp Laos. 73 class Program { static void Main(string[] args) { sayHello("World"); Console.ReadKey(); } [Trace] static void sayHello(string name) { Console.WriteLine("Hello, {0}.", name); } } Exemplo 16 – Exemplo de aplicação de um atributo e um método. 3.12.5 Conclusões Na conclusão da comparação entre o AspectJ e o Postsharp LAOS verifica-se que no PostSharp LAOS todo o entrelaçamento é feito em tempo de compilação. As limitações apresentadas pelo PostSharp LAOS ao nível de join points, point cuts e advices, podem ser combatidas utilizando o PostSharp Core. Esta ultima solução é complexa e requer vastos conhecimentos técnicos e aprofundados da tecnologia PostSharp e da Framework .Net. Existem alguns projectos que correntemente utilizam a tecnologia PostSharp com sucesso. O PostSharp4EF34 é um exemplo de que as limitações que o Postsharp Laos apresenta podem ser combatidas utilizando o PostSharp Core desenvolvendo plugins próprios. Por sua vez, o AspectJ possui um vasto leque de join points e mais tempo de existência consistindo nas razões pelas quais lhe é conferida uma maior solidez. Contudo possui uma 34 http://www.codeplex.com/efcontrib (Acedido em Setembro de 2008) 74 linguagem própria para a definição de pointcuts e advices que adiciona alguma complexidade, embora ao mesmo tempo torne a tecnologia mais flexível. O PostSharp é relativamente novo mas não possui linguagem própria, sendo mais simples de usar, embora mais limitado. 3.13 MODULARIZAÇÃO DE PADRÕES DE DESENHO Padrões de desenho Gang-of-Four [Gamma, Helm et al. 1995] (GOF) oferecem soluções flexíveis para problemas comuns no desenvolvimento de software orientado a objectos. Cada padrão é composto por um conjunto de partes que incluem: a intenção, a aplicabilidade, a estrutura da solução e um exemplo de implementação. Na figura 20, pode-se observar a implementação do padrão de desenho Observer. Este padrão de desenho permite que uma ou mais classes se registem para receber notificações e que outras classes enviem notificações a essas classes. Para permitir este comportamento é necessário adicionar métodos às classes que participam no padrão, ou seja, as classes que se registam e as classes que são notificadas. 75 Figura 20 – Padrão de Desenho Observer implementado em POO A maioria dos padrões de desenho GOF envolve estruturas transversais na relação do padrão de desenho com as classes que participam nele. A implementação dos padrões de desenho tem efeitos no código do sistema, pois estes influenciam a sua estrutura. A sua implementação mistura-se com o código das classes, o que implica a perda de modularidade, dificultando deste modo, a tarefa de distinguir o sistema do padrão de desenho. Adicionar um padrão de desenho é uma tarefa invasiva e difícil de reverter. Enquanto que o padrão de desenho é reutilizável, a sua implementação num sistema não o é. Hannemann e Kiczales [Hannemann and Kiczales 2002] elaboraram um estudo, que compara a implementação de 23 padrões de desenho, desenvolvidos em POA na linguagem de programação AspectJ e em POO utilizando a linguagem de programação Java. As principais 76 ilações alcançadas pelos autores evidenciaram que 74 % dos padrões de desenho implementados utilizando POA, eram mais modulares e que 52% dos padrões se tornaram reutilizáveis noutros projectos. Sant’Anna [Garcia, Sant'Anna et al. 2005] complementando o estudo anterior adicionou dados quantificados, utilizando métricas que permitem medir a separação de concerns, coesão e tamanho do código. As principais conclusões apontaram que nestes padrões de desenho, em particular a utilização de POA, ajuda a separação de concern, embora em alguns casos o acréscimo de operações aumente a complexidade interna do sistema e existam mais linhas de código por padrão de desenho. Na figura 21 é possível observar o desenho do sistema para a implementação do padrão de desenho Observer recorrendo a POA. Figura 21 – Padrão de desenho Observer implementado em POA 3.13.1 Exemplo Observer Pattern. No Anexo 6 é apresentada uma possível implementação para o sistema desenhado na figura 21. 77 3.13.2 Análise dos Estudos Hannemann e Kiczales [Hannemann and Kiczales 2002] analisaram 23 padrões de desenho sendo os resultados apresentados na tabela 9. Propriedades Modulares Padrão de Desenho Localizável Reutilizável Composição Transparente Removível Implementações idênticas Façade Abstract Factory Não não Não não Bridge não não Não não Builder não não Não não Factory Method não não Não não Interpreter não não n/a não Template Method (sim) não Não (sim) Adapter sim não Sim sim State (sim) não n/a (sim) Decorator sim não Sim sim Proxy (sim) não (sim) (sim) Visitor (sim) sim Sim (sim) Command (sim) sim Sim sim Composite sim sim Sim (sim) Iterator sim sim Sim sim Flyweight sim sim Sim sim Memento sim sim Sim sim Strategy sim sim Sim sim Mediator sim sim Sim sim Chain of Responsibility sim sim Sim sim Prototype sim sim (sim) sim Singleton sim sim n/a sim Observer sim sim Sim sim Tabela 6 – Resumo das comparações dos padrões de desenho. A maioria dos padrões de desenho tornam-se localizáveis e removíveis sendo a sua composição transparente no sistema, implementando assim, vantagens na utilização da POA. Embora aparentemente existam vantagens para o programador, é necessário quantificá-las utilizando métricas que permitam verificar que existem melhorias ao nível da coesão, ligação e 78 tamanho do código desenvolvido. No estudo levado a cabo por Sant’Anna [Garcia, Sant'Anna et al. 2005], utilizando métricas bem conhecidas, não foram obtidas conclusões gerais. Para estes padrões de desenho em concreto e efectuando a comparação entre as duas linguagens AspectJ e Java, o uso de aspectos ajudou a melhorar a ligação e coesão de alguns padrões de desenho, enquanto outros padrões aumentam a complexidade e desenho do sistema. 3.14 MODULARIZAÇÃO DE EXCEPÇÕES Os mecanismos de excepção permitem separar, de forma modular, o código normal do código para tratar situações excepcionais. Isto é, quando existe um acontecimento que não é planeado, é necessário criar código que o permita tratar e se possível continuar o programa de forma normal. Em POO este comportamento é alcançado através da utilização de excepções. Algumas considerações em relação a POA para a modularização do tratamento de excepções foram elaboradas por Filho [Castor Filho, Cacho et al. 2006]: • Que atributos a POA promove e melhora para além da separação de concerns, por exemplo: melhora a ligação entre módulos, a coesão, o tamanho e o número de linhas de código, dos módulos? • O tratamento de excepções em aspectos é reutilizável em sistemas de software de produção? • Quando é benéfico utilizar aspectos para o tratamento de excepções e quando não o é? 79 • Como é que os aspectos de tratamento de excepções afectam outras implementações de aspectos de outros concerns. A modularização de excepções utilizando POA consiste assim, em mover os blocos de código referente ao tratamento das excepções para aspectos, mantendo o código normal no método. No exemplo 17 é mostrada a estrutura típica de um método que trata uma excepção. public class C { void m() { try { //código normal }catch (Exception E) { //tratamento da Excepção } } } Exemplo 17 – Código típico num método para o tratamento de uma excepção. Por sua vez, a modularização para aspectos consiste em primeiro manter no método o código normal e remover o tratamento da excepção (exemplo 18). public class C { void m() { //código normal } } Exemplo 18 – Código POO sem tratamento de excepções. Deste modo, move-se todo o código referente ao tratamento para um aspecto, definindo um pointcut para interceptar esse método, um advice que permita executar (proceed) o código normal do referido método e em caso de ocorrer um erro tratar o mesmo dentro do advice (exemplo 19). 80 public aspect A { pointcut pcd() : execution(void C.m()); void around() : pcd() { try { proceed(); } catch(Exception e) { //tratamento da excepção } } declare soft : Exception : pcd(); } Exemplo 19 – Tratamento de Excepções. No método mantém-se o código normal e nos aspectos o código que trata das possíveis excepções, utiliza-se um advice para cada bloco try, englobando se possível no mesmo advice os vários blocos catch referentes a esse bloco try, utilizando-se o tipo de advice invés. No advice e no bloco try o operador proceed permite a execução do join point original. No caso de ocorrer uma excepção, os blocos catch definidos no advice tratam a excepção. Para evitar os erros em tempo de compilação, uma vez que o Java exige o tratamento obrigatório da maioria das excepções, utiliza-se o enfraquecimento de excepções (Anexo 1) para ultrapassar essa limitação. Castor Filho [Castor Filho, Garcia et al. 2005; Castor Filho, Cacho et al. 2006] testou a viabilidade da utilização da POA para modularizar o tratamento das excepções, quantificando os resultados utilizando métricas bem definidas, nomeadamente ligação, coesão e tamanho dos módulos. A análise dos testes efectuados no(s) artigo(s) sugere a utilização da POA para a modularização de excepções. Contudo, embora melhore a separação de concerns, tem desvantagens. Se o código para o tratamento da excepção não é uniforme e é extremamente dependente do contexto, ou muito complexo, a modularização recorrendo a POA pode ser mais prejudicial do que benéfica. Para um uso efectivo da POA é necessário um planeamento a priori 81 que tem de ser incorporado no processo de planeamento e desenvolvimento de software, para assim conseguir prever o impacto da modularização. 3.15 FRAMEWORKS Para além de linguagens de POA, existem frameworks que implementam o paradigma da POA de modo a facilitar a adição modular de funcionalidades. No anexo 5 referenciam-se duas Frameworks que utilizam a POA para oferecer novas formas de modularidade. 3.16 MÉTRICAS As métricas de software são o método mais efectivo para fornecer evidências empíricas, pois podem contribuir para o melhor entendimento das diferentes dimensões da complexidade do software [Briand, El Emam et al. 1995]. A separação de concerns de forma modular é vantajosa na maneira de pensar e implementar um software complexo, mas tem de existir resultados quantificados que demonstrem quais são as boas práticas de desenho e de implementação na utilização do Paradigma Orientado a Aspectos. Sant’Anna [Sant'Anna, Garcia et al. 2003] utiliza um conjunto de métricas para analisar o desempenho da POA em relação a POO. Essas métricas, segundo ele, devem satisfazer os seguintes requisitos: 82 • Quantificar atributos de software bem conhecidos, tais como: a separação de concerns, ligação, coesão e tamanho; • Depender o mais possível das métricas tradicionais e de extensões das métricas de POO para POA; • Capturar as diferentes dimensões da ligação e coesão do software orientado a aspectos; • Suportar a identificação dos benefícios e desvantagens da utilização de aspectos num projecto de software, quando comparado com uma solução orientada a objectos para o mesmo problema. É proposto pelo autor um conjunto de métricas e um modelo (figura 35), que é composto por três diferentes elementos, e aos quais estão interligadas as métricas definidas. Os atributos do modelo são: • Qualidades: são os atributos que primariamente se desejam num sistema software; • Factores: são as qualidades de atributos secundários que influenciam os primeiros atributos definidos; • Atributos Internos: estão ligados a princípios bem definidos da engenharia de software, sendo essenciais para alcançar a qualidade dos factores. 83 Figura 22 - Modelo de Qualidade. Neste âmbito, é elaborado pelos autores um conjunto de seis cenários evidenciando comparações entre POO e POA utilizando as várias métricas (tabela 10). Resultados dos Cenários Evolução Reutilização Entidades Alteradas Operações alteradas Entidades Adicionada s Operações Adicionada s Relações alteradas Relações Adicionada s LOCs adicionados LOCs alterados Entidades copiadsa LOCs copiados O AO O AO O AO O AO O AO O AO O AO O AO O AO O AO S1 1 1 3 3 5 5 2 3 0 0 15 15 101 98 1 1 - - - - S2 0 0 2 2 4 4 0 0 0 0 10 10 84 86 0 0 - - - - S3 0 0 2 2 4 4 0 0 0 0 10 10 84 86 0 0 0 0 0 0 S4 0 0 2 3 8 8 0 0 0 0 29 25 188 67 0 8 0 0 0 0 S5 1 1 2 1 0 0 1 1 0 0 4 2 16 14 0 0 0 0 6 6 S6 0 0 0 0 0 0 0 0 0 0 0 0 15 15 0 0 - - - - S7 5 1 0 0 0 0 0 0 5 2 1 1 0 0 5 1 0 0 40 0 Tabela 7 – Evolução da utilização de POO e POA, segundo as métricas de Sant’Anna. Noutro artigo, Ceccato [Ceccato and Tonella 2004] evolui as métricas usadas em POO e introduz novas, sendo que estas ajudam, na opinião do autor, a avaliar o efeito da POA: 84 • WOM (Weighted Operations in Module): Número de operações num determinado módulo; • DIT (Depth of Inheritance Tree): Comprimento do caminho mais longo para um determinado módulo até a raiz da classe/aspecto da hierarquia; • NOC (Number Of Children): Número de subclasses ou sub-aspectos que um determinado módulo possui; • CAE (Coupling on Advice Execution): Número de aspectos que contêm advices que sejam executados no disputar da execução de uma determinada operação num dado módulo; • CIM (Coupling on Intercepted Modules): Número de módulos ou interfaces explicitamente definidas num pointcut de um dado aspecto; • CMC (Coupling on Method Call): Número de módulos ou interfaces que declaram métodos que são possíveis de ser invocados por um dado módulo; • CFA (Coupling on Field Access): Número de módulos ou interfaces que declaram atributos que são acedidos por um dado módulo; • RFM (Response For a Module): Métodos e advices potencialmente executados em reposta a uma mensagem recebida por um dado módulo; • LCO (Lack of Cohesion in Operations): Par de operações que trabalham em atributos de classes diferentes menos pares de operações que trabalham em atributos comuns (zero se negativo); • CDA (Crosscutting Degree of an Aspect): número de módulos afectados por um pointcut e pela introdução de um dado aspecto. 85 Para testar as métricas desenvolveram uma ferramenta, usando o exemplo de padrões de desenho observer (anexo 6) [Hannemann and Kiczales 2002]. Os resultados (tabela 8 e 9) comparam a implementação AspectJ com a implementação em Java. Versão Java AspectJ WOM DIT 3 1 NOC 1 2 CAE 0 0 CIM 0 0 0 2 Tabela 8 - Comparação de métricas entre Java e AspectJ primeira parte. Versão Java AspectJ CMC CFA 2 1 RFM 0 0 LCO 7 2 CDA 1-12 0 0 3 Tabela 9 - Comparação de métricas entre Java e AspectJ segunda parte. Nos resultados é possível visualizar uma melhoria dos valores no global devido ao uso de um aspecto abstracto, mas o DIT aumentou, já que existem aspectos que herdam de outros aspectos. 86 4. CONSIDERAÇÕES FINAIS Nesta dissertação procurou-se abordar de forma prática a linguagem de programação AspectJ, a tecnologia Postsharp e as suas aplicações recorrendo a estudos científicos que permitem validar a aplicabilidade das tecnologias POA. Assim, as principais contribuições deste trabalho consistem na síntese da história das tecnologias que envolvem separação de concerns, descrição e análise dos estilos da linguagem de programação AspectJ e Postsharp. Este trabalho é ainda enriquecido com a apresentação de pesquisas no campo da modularização de padrões de desenho, modularização de excepções e métricas de comparação. A abordagem seguida foi da apresentação de exemplos práticos e alusões a aplicações reais de situações onde estes se podem aplicar. A tecnologia POA permite adicionar mecanismos para melhorar a implementação de crosscutting concerns através de vários tipos de aproximações, código fonte, código intermédio e máquinas virtuais. Estas tecnologias visam alcançar a separação de concerns permitindo implementar vários tipos de crosscutting concerns modularmente. As tecnologias foram evoluindo e presentemente existem ferramentas que já possuem integração nos mais variados tipos de ambientes de desenvolvimento, instalando-se como extensões naturais às linguagens existentes nesses ambientes. Estas ainda precisam de algum tempo para colmatar algumas falhas e apresentar ferramentas que permitam solucionar alguns problemas e melhorar a experiência do 87 programador, de maneira a permitir a depuração, garantir a exacta aplicação dos aspectos, bem como, manter a aplicabilidade dos aspectos quando existe a alteração ou evolução do código base, por exemplo através da utilização de refactoring. Para tal, são necessários estudos que sejam conclusivos e demonstrados com métricas quantitativas que permitam definir exactamente que tipo de crosscuting concerns a POA, tecnologicamente falando, melhora. Isto é, tem de existir a garantia que a nova forma de modularização não acarreta prejuízos tecnológicos. Podem-se enumerar alguns problemas que a POA acarreta, devido à falta de ferramentas ou à sua complexidade, tais como: • Complexidade: como todas as novas tecnologias existe uma curva de aprendizagem aos novos conceitos. Os vários estilos de programação que existem em POA uns mais complexos que outros, com o tempo e o amadurecimento das linguagens de programação diminuirão a complexidade; • Que tipos de comportamentos crosscutting são possíveis desenvolver, para além de transacções e registos; • Padrões de desenho ou de frameworks evitam o uso de POA: estas técnicas e tecnologias não são uma solução genérica como a POA; • Depuração: a depuração de aspectos é problemática, e em qualquer tecnologia a depuração é uma tarefa difícil sem as ferramentas adequadas, para tal em POA devem-se desenvolver ferramentas que permitam uma depuração mais simplificada; • A evolução dos sistemas pode ser afectada pelos aspectos já desenvolvidos: a forma de desenvolver pointcuts pode limitar a evolução das classes ou criar um sistema onde os aspectos vão ter um comportamento incerto, já que as regras de 88 captura podem não se aplicar. As ferramentas de desenvolvimento devem ajudar a prevenir estas situações suportando avisos ou refactoring para adaptar os aspectos; • Segurança, como prevenir que sistemas possam ser estendidos através de POA? Como benefícios da utilização do Paradigma Orientado as Aspectos podem-se enumerar: • Código mais limpo: ao ser necessário só ter em consideração uma área de cada vez é mais fácil de perceber e editar o código escrito; • Modularização superior: todos os concerns dos sistemas são separados de acordo com a sua forma natural; • Fácil de evoluir: modificações para melhorar a performance de um sistema ou regras de negócio são facilmente acopladas ao sistema; • Mais código reutilizável: a eliminação do efeito de intrusão e espalhamento permite que os módulos sejam facilmente acoplados a outros sistemas de software; • Rápido de implementar: fácil divisão de tarefas e desenvolvimento paralelo em separado; • Redução de custos: a possibilidade de desenvolver mais rapidamente e o aumento da reutilização tem como efeito a redução de custos; Existe um grande esforço e dedicação da comunidade académica na investigação nas várias áreas da DSOA, que trabalham para colmatar a falta de ferramentas e modelos para as várias etapas do desenvolvimento de software. Este esforço começa a ser recompensado com a 89 crescente implementação de tecnologias como o Postsharp e inclusão de Weavers em frameworks populares demonstrando que os problemas da tecnologia estão a ser corrigidos e que os conceitos mais práticos são viáveis, acarretando benefícios ao nível comercial nomeadamente na redução de tempo de implementação e custos, o que faz prever que a tecnologia continue a evoluir e amadurecer nas suas diversas áreas. O presente trabalho abre caminho como base de introdução à tecnologia POA na tentativa de se conhecer o problema que se tenta resolver, o modelo e as ferramentas que existem para o resolver, podendo ainda ser enriquecido com a adição de novos estudos quantitativos, tecnologias POA emergentes, outras formas de desenvolvimento e exemplo de aplicações reais da tecnologia. 90 5. BIBLIOGRAFIA Aksit, M., K. Wakita, et al. (1993). Abstracting Object-Interactions Using Composition-Filters. Object-Based Distributed Processing. R. Guerraoui, O. Nierstrasz and M. Riveill, Springer-Verlag Lecture Notes in Computer Science: 152-184. Backus, J. W., R. J. Beeber, et al. (1957). "The FORTRAN automatic coding system." Western Joint Computer Conference: 188-198. Berard, E. V. (2002). "Abstraction, Encapsulation, and Information Hiding." Bergmans, L. and M. Aksit (2001). "Composing Crosscutting Concerns Using Composition Filters." Comm. ACM 44(10): 51--57. Booch, G. (1994). Object-oriented analysis and design with applications, Benjamin/Cummings Pub. Co Redwood City, Calif. Booch, G. (2001). "Through the Looking Glass." Software Development, July. Briand, L., K. El Emam, et al. (1995). "Theoretical and Empirical Validation of Software Product Measures." International Software Engineering Research Network, Technical Report ISERN-95-03. Brown, A. W. and K. C. Wallnau (1998). "The current state of CBSE." Software, IEEE 15(5): 37-46. Castor Filho, F., N. Cacho, et al. (2006). "Exceptions and aspects: the devil is in the details." Proceedings of the 13th ACM SIGSOFT 14th international symposium on Foundations of software engineering: 152-162. Castor Filho, F., A. Garcia, et al. (2005). "A quantitative study on the aspectization of exception handling." Proceedings of the ECOOP’2005 Workshop on Exception Handling in ObjectOriented Systems. 91 Ceccato, M. and P. Tonella (2004). "Measuring the Effects of Software Aspectization." Proc. of the 1st Workshop on Aspect Reverse Engineering (CD-ROM), The Netherlands. Clement, A., A. Colyer, et al. (2003). "Aspect-Oriented Programming with AJDT." ECOOP Workshop on Analysis of Aspect-Oriented Software. Czarnecki, K., U. W. Eisenecker, et al. Beyond Objects: Generative Programming. Dijkstra, E. W. (1968). " Go To Statement Considered Harmful." Communications of the ACM 11: 147-148. Dijkstra, E. W. (1976). A discipline of programming. Englewood Cliffs, New Jersey, PrenticeHall. Elrad, T., M. Aksit, et al. (2001). "Discussing Aspects of AOP." Comm. ACM 44(10): 33--38. Elrad, T., R. E. Filman, et al. (2001). "Aspect-Oriented Programming." Comm. ACM 44(10): 29-32. Filman, R. E. and D. P. Friedman (2000). "Aspect-Oriented Programming is Quantification and Obliviousness." Workshop on Advanced Separation of Concerns 2000. Floyd, R. W. (1979). "The paradigms of programming." Communications of the ACM 22(8): 455-460. Fraiteur, G. (2008). "User-friendly aspects with compile-time imperative semantics in .NET: an overview of PostSharp." 9. Gamma, E., R. Helm, et al. (1995). Design patterns: elements of reusable object-oriented software. Garcia, A., C. Sant'Anna, et al. (2005). "Modularizing design patterns with aspects: a quantitative study." Aspect-oriented software development: Proceedings of the 4 th international conference on Aspect-oriented software development 14(18): 3-14. Hannemann, J. and G. Kiczales (2002). Design pattern implementation in Java and AspectJ. Proceedings of the 17th ACM conference on Object-oriented programming, systems, languages, and applications, ACM Press. Harrison, W. and H. Ossher (1993). Subject-Oriented Programming---A Critique of Pure Objects. Proc. 1993 Conf. Object-Oriented Programming Systems, Languages, and Applications. 92 Harrison, W., H. Ossher, et al. (2005). "Concern modeling in the concern manipulation environment." Proceedings of the 2005 workshop on Modeling and analysis of concerns in software: 1-5. Harrison, W., H. Ossher, et al. (2005). "Supporting aspect-oriented software development with the Concern Manipulation Environment." IBM Systems Journal 44(2): 309-318. Hayes, B. (2003). "THE POST-OOP PARADIGM." American Scientist 91(2). Hilsdale, E. and J. Hugunin (2004). "Advice weaving in AspectJ." Proceedings of the 3rd international conference on Aspect-oriented software development: 26-35. Hürsch, W. L. and C. V. Lopes (1995). Separation of Concerns. Boston, MA, College of Computer Science, Northeastern University. Kaplan, M., H. Ossher, et al. (1996). Subject-Oriented Design and the Watson Subject Compiler. Proc. OOPSLA'96 Workshop on Subjectivity. Kernighan, B. W. and D. M. Ritchie (1988). The C programming language, Prentice-Hall. Kiczales, G. (1996). "Beyond the black box: Open implementation." {IEEE} Software 13(1): 8-11. Kiczales, G., J. des Rivieres, et al. (1991). The Art of the Metaobject Protocol. Cambridge, Massachusetts, MIT Press. Kiczales, G. and E. Hilsdale (2001). Aspect-oriented programming. Proceedings of the 8th European Software Engineering Conference held jointly with 9th Acm Sigsoft Symposium on Foundations of Software Engineering, ACM Press. Kiczales, G., E. Hilsdale, et al. (2001). "Getting Started with AspectJ." Comm. ACM 44(10): 59-65. Kiczales, G., E. Hilsdale, et al. (2001). An overview of AspectJ. Proc. ECOOP 2001, LNCS 2072, Berlin, Springer-Verlag. Kiczales, G., J. Lamping, et al. (1997). Aspect-Oriented Programming. 11th Europeen Conf. Object-Oriented Programming, Springer Verlag. Kiczales, G. and M. Mezini (2005). Aspect-oriented programming and modular reasoning. ICSE '05: Proceedings of the 27th international conference on Software engineering, New York, ACM Press. Kuhn, T. S. (1970). The Structure of Scientific Revolutions, University of Chicago Press. 93 Laddad, R. (2003). "AspectJ in Action." 512. Lieberherr, K., D. Orleans, et al. (2001). "Aspect-Oriented Programming with Adaptive Methods." Comm. ACM 44(10): 39--41. Lieberherr, K. J. (1996). Adaptive Object-Oriented Software: the Demeter Method with Propagation Patterns, PWS Publishing Company, Boston. Lindholm, T. and F. Yellin (1999). Java Virtual Machine Specification, Addison-Wesley Longman Publishing Co., Inc. Boston, MA, USA. Liskov, B. H. and J. M. Wing (2001). "Behavioural subtyping using invariants and constraints." Lopes, C. V. (1996). Adaptive parameter passing. 2nd Int'l Symposium on Object Technologies for Advanced Software, Springer-Verlag. Lopes, C. V. (1997). D: A Language Framework for Distributed Programming, College of Computer Science, Northeastern University. Lopes, C. V. (2002). AOP: A Historical Perspective (What's in a Name?): 97-122. Löwy, J. (2003). Programming. NET Components, O'Reilly. Maes, P. (1987). "Concepts and experiments in computational reflection." Mehmet Aksit, K. W., Jan Bosch, Lodewijk Bergmans, Akinori Yonezawa (1994). "Abstracting Object Interactions Using Composition Filters." Mendhekar, A., G. Kiczales, et al. (1997). RG: A Case-Study for Aspect-Oriented Programming, Palo Alto Research Center. Okamura, H. and Y. Ishikawa (1994 ). "Object Location Control Using Meta-level Programming." 299 - 319 Ossher, H. and P. Tarr (1999). Multi-Dimensional Separation of Concerns using Hyperspaces, IBM Research Report. Ossher, H. and P. Tarr (2000). "Hyper/J: multi-dimensional separation of concerns for Java." Proceedings of the 22nd international conference on Software engineering: 734-737. Ossher, H. and P. Tarr (2001). "The Shape of Things To Come: Using Multi-Dimensional Separation of Concerns with Hyper/J to (Re)Shape Evolving Software." Comm. ACM 44(10): 43-50. Parnas, D. L. (1972). "On the Criteria To Be Used in Decomposing Systems into Modules." Comm. ACM 15(12): 1053-1058. 94 Rashid, A., P. Sawyer, et al. (2002). "Early aspects: a model for aspect-oriented requirements engineering." Requirements Engineering, 2002. Proceedings. IEEE Joint International Conference on: 199-202. Rumbaugh, J., I. Jacobson, et al. (1996). "The Unified Modeling Language." Documentation Set 1. Sant'Anna, C. a., A. Garcia, et al. (2003). On the Reuse and Maintenance of Aspect-Oriented Software: An Assessment Framework. XVII Brazilian Symposium on Software Engineering. Smith, B. C. (1982). "Procedural Reflection in Programming Languages." 762. Smith, B. C. (1985). "Prologue to "Reflection and Semantics in a Procedural Language''." Szyperski, C. (1998). Component Software: Beyond Object-oriented Programming, AddisonWesley Professional. Szyperski, C., J. Bosch, et al. (1999). "Component-Oriented Programming." Object-Oriented Technology: ECOOP'99 Workshop Reader: ECOOP'99 Workshops, Panels, and Posters, Lisbon, Portugal, June 14-18, 1999: Proceedings. Tarr, P., H. Ossher, et al. (1999). N Degrees of Separation: Multi-Dimensional Separation of Concerns. Proc. 21st Int'l Conf. Software Engineering (ICSE'1999), IEEE Computer Society Press. Wirth, N. (1971). "The programming language pascal." Acta Informatica 1(1): 35-63. 95 6. ANEXOS 96 ANEXO 1: LINGUAGEM ASPECTJ 6.1 PRESSUPOSTOS Para a demonstração prática da linguagem utilizar-se-á o diagrama UML [Rumbaugh, Jacobson et al. 1996] (figura 23). Figura 23 – Diagrama UML do exemplo. Para a elaboração dos exemplos foi utilizado o Eclipse 3.4, o java SDK 6 actualização 6 e AspectJ 1.6.0. 97 6.2 JOIN POINT Um elemento crítico no desenho de uma linguagem orientada a aspectos é o modelo de join point (Pontos de Junção). Este modelo permite disponibilizar um quadro comum de referência para definir a estrutura dos crosscutting concerns [Kiczales, Hilsdale et al. 2001]. Em AspectJ, join points são pontos bem definidos no fluxo do programa em execução. São disponibilizados pela linguagem acesso a vários tipos de join points, os que são mais frequentemente utilizados, visam capturar: chamadas aos métodos, capturar o contexto exterior do método e capturar o despontar de um método dentro do contexto de outro método. 6.3 POINTCUT PointCut (Secção) permite definir conjuntos de join points e expor informações de contexto desses join points. Por exemplo, o seguinte pointcut “call (void Point.setX (int)) ” intercepta chamadas ao método setX da classe Point com argumento de entrada tipo int e sem argumento de saída, void, o tipo de join point usado foi o call que permite capturar chamadas a métodos. Em AspectJ a definição de pointcuts é definida pelo programador (exemplo 20). pointcut nome_do_pointcut ( arg1, ..., argN ) : definição de join points Exemplo 20 - sintaxe para definir um pointcut em AspectJ. Na definição (exemplo 20) nome_do_pointcut é o nome atribuído pelo programador para identificar o pointcut, que cuja interface (a forma de interagir com o pointcut) tem por 98 argumentos “arg1, …, argN”, os Join points encontram-se declarados na parte da definição. Existem vários tipos de operadores que identificam os vários Join points. O Join Point é associado a um ponto no fluxo do programa, para por exemplo se capturar uma chamada a um método, declara-se a assinatura do método a ser capturado (exemplo 21). pointcut secção_setxPoint: call(void Point.setX(int)); Exemplo 21 – Exemplo de declaração de um pointcut. 6.3.1 Métodos e Construtores AspectJ disponibiliza dois tipos primitivos para capturar a chamada e execução de métodos ou construtores de classes. Chamadas a métodos e/ou construtores Efectua a captura de chamadas a métodos ou construtores, a captura ocorre depois da avaliação dos argumentos e, antes ou depois da execução do bloco de código do método ou construtor. Utiliza-se o operador call, que identifica o join point para capturar chamadas, fornecendo a assinatura do método ou construtor a ser capturado. No exemplo 22, é capturada a chamada ao método setX da classe Point com argumento de entrada do tipo int e sem argumento de saída. call( void Point.setX(int) ) Exemplo 22 – Intercepção de métodos. Para capturar chamadas a um construtor de uma classe, ao invés de se declarar o nome do método, como no exemplo anterior, é definido pela linguagem que se deve utilizar o termo ‘new’ e removido o argumento de saída da assinatura (exemplo 23). 99 call( Point.new(int, int) ) Exemplo 23 – Intercepção de um construtor de uma classe. Execução de métodos e ou construtores A intercepção da execução de métodos ou construtores permite a captura do inicio ou do fim da execução do bloco de código interno desse método. A execução de métodos ou construtores é definida utilizando o operador execution, fornecendo a assinatura do método ou construtor (exemplo 24). execution( void Point.setX(int) ) Exemplo 24 – Intercepção da execução de um método. Para definir o mesmo tipo de comportamento para um construtor de uma classe, é utilizado o termo ‘new’ invés do nome do método e é removido o argumento de saída, analogamente ao que acontece com o pointcut do tipo ‘call’ (exemplo 25). execution( Point.new(int, int) ) Exemplo 25 – Intercepção de um construtor. 6.3.2 Atributos Atributos são variáveis definidas em classes, é possível capturar o acesso de leitura ou de escrita a atributos estáticos e não estáticos. Para capturar o acesso de leitura a um atributo utiliza-se o operador get (exemplo 26) e para capturar acessos de escrita, o operador set, junto com a assinatura do atributo (exemplo 27). get (Point.nome_atributo) Exemplo 26 - Intercepção do acesso de leitura a um atributo da classe Point. 100 set (Point.nome_atributo) Exemplo 27 – Intercepção do acesso de escrita a um atributo da classe Point. 6.3.3 Tratamento de excepções Excepções permitem o tratamento de situações anómalas que ocorram durante a execução de um certo bloco de código. Para capturar a execução do tratamento de uma excepção, o bloco de código dentro da cláusula ‘catch’, utiliza-se o operador ‘Handler’ fornecendo o tipo da excepção (exemplo 28). handler( DataFormatException ) Exemplo 28 – Intercepção do tratamento de uma excepção. 6.3.4 Inicialização estática A inicialização estática permite definir valores, que são atribuídos aquando da execução do programa. Para capturar a inicialização de código estático utiliza-se o operador ‘staticinitialization’ junto com o tipo, por exemplo o nome de uma classe (exemplo 29). staticinitialization( Point ) Exemplo 29 – Inicialização estática. 6.3.5 Inicialização de objectos AspectJ permite capturar a execução da inicialização de objectos através dos operadores ‘initialization’ e ‘preinitialization’ fornecendo o nome da classe e a palavra ‘new’ que identifica o construtor da classe. No exemplo 30 captura-se a inicialização de objectos: 101 initialization(Point.new(..)) Exemplo 30 – Inicialização de Objectos. No exemplo 31 captura-se da pré-inicialização de objectos: preinitialization(Point.new(..)) Exemplo 31 – Pre-inicialização de Objectos. 6.3.6 Lógica Booleana AspectJ permite agrupar vários Join Points num só pointcut, permitindo uma elaboração minuciosa na definição de situações de captura. Para agrupar vários join points utiliza-se a lógica booleana. • Intercessão, definida pelos símbolos ‘&&’; • União, definida pelos símbolos ‘||’; • Inversão, definida pelo símbolo ‘!’. No exemplo 32, captura-se a chamada ao método setX ou ao método setY. pointcut secção_set(): call(void Point.setX(int)) || call(void Point.setY(int) Exemplo 32 – Agrupar vários Join Points. É possível construir condições lógicas com join points utilizando o operador de decisão if. Permitindo limitar a captura de join points com base num certo contexto. No exemplo 33, captura-se a chamada ao método setX, na classe Point cujo argumento de entrada seja do tipo int e este for de valor inferior a 0. call(void Point.setX(int)) && args(x) && if(x<0) Exemplo 33 - Utilização do operador, if. 102 6.3.7 Exposição do contexto É possível ter acesso ao contexto, ou seja, à informação de um determinado join point durante a execução do programa. Por exemplo é possível ter acesso aos argumentos, de entrada ou de saída, de um método. Os operadores que o permitem são this, target, e args. Estes operadores tem dupla funcionalidade, funcionam como join point de selecção e expor o contexto. No exemplo 34, define-se a captura de todos os métodos que tenham um único argumento de entrada do tipo int, permitindo o acesso a esse argumento que está declarado na interface do pointcut. pointcut intArg(int i): args(i) Exemplo 34 – Pointcut args. Utilizando a lógica booleana é possível definir clausulas mais especificas. No exemplo 35, captura-se a chamada ao método setX e utiliza-se o operador args, para obter acesso ao argumento de entrada, tipo int, do método para por exemplo, implementar uma restrição nos valores que a função aceita. pointcut conjunto_setxPoint(int x):call(void Point.setX(int))&& args(x); Exemplo 35 – Utilização da lógica booleana em pointcuts. É possível também utilizar o operador de decisão if. No exemplo 36, utiliza-se o operador if para filtrar o acesso ao contexto. Permitindo definir minuciosamente o pointcut, assim só serão capturadas todas as chamadas ao método setX da classe Point cujo argumento de entrada tenha um valor inferior a zero. 103 Pointcut conjunto_setxPoint(int x):call(void Point.setX(int)) && args(x) && if(x<0) Exemplo 36 – Utilização do operador If. Para capturar um tipo de objecto é possível utilizar o operador this fornecendo o nome da classe. No exemplo 37, captura-se todos os join points que ocorram durante a execução a instâncias do tipo Point, não captura contexto estático. É um comportamento dinâmico. this(Point) Exemplo 37 – Operador this. Para capturar join points num objecto existe o operador target e que fornece o nome da classe. No exemplo 38, captura-se todo o comportamento da classe Point, chamadas a métodos e atribuições de atributos. É um comportamento estático. target(Point) Exemplo 38 – Operador target. 6.3.8 Redefinir assinaturas AspectJ possibilita utilizar símbolos (Wildacards) para ajudar a definir as assinaturas dos pointcuts, a tabela 10 mostra os símbolos permitidos. Símbolo “..” “*” “+” Descrição Quaisquer números e tipo de argumentos. • Quaisquer palavras seguintes; • Tipo de argumento de saída arbitrário; • Tipo arbitrário de classe ou método. Quaisquer subclasses. Tabela 10 – Símbolos permitidos nas assinaturas. 104 No exemplo 39, é definido um pointcut que permite capturar a execução de métodos com o nome “m” com um número arbitrário de argumentos de entrada, utilizando o símbolo “..”, e sem argumentos de saída. pointcut allM(): execution(void m(..)) Exemplo 39 – Utilização do símbolo “..”. No exemplo 40, captura-se qualquer chamada a método (s) de nome “m” da classe “MyClass” com quaisquer tipo de argumento de saída, utilizando o símbolo “*”, e quaisquer número de argumentos de entrada, utilizando o símbolo “..”. pointcut allM(): call(* MyClass.m (..)) Exemplo 40 – Utilização do símbolo “*” e “..”. No exemplo 41, o símbolo “*” é utilizado para captura o método “mover”, este pode estar implementado em quaisquer classe do programa, com quaisquer tipo de argumento de saída, e utilizando o símbolo “..”, qualquer número e tipo de argumentos de entrada. pointcut mover(): call(* *.mover(..)) Exemplo 41 – Utilização do símbolo “*” e “..”. No exemplo 42, permite utilizar o símbolo “*”, para capturar qualquer método da classe com quaisquer tipos de argumentos de saída e quaisquer números e tipo de argumentos de entrada. pointcut mover(): call(* Classe.*(..)) Exemplo 42 – Utilização do símbolo “*”. No exemplo 43, o símbolo “*” é utilizado para completar o nome do método, capturando qualquer tipo de argumento de saída e quaisquer tipo e número de argumentos de entrada de qualquer método da classe, que comece por “set”, por exemplo agrupará setX e setY, etc. 105 pointcut mover(): call(* Classe.set*(..)) Exemplo 43 - Utilização do símbolo “*”. No exemplo 44, o símbolo “*” é utilizado para seleccionar qualquer tipo de atributo da classe que comece por “id” (idCliente, idXpto, etc.). pointcut id(): get(* Classe.id*) Exemplo 44 - Utilização do símbolo "*". No exemplo 45, utiliza-se o símbolo “+” para capturar a chamada ao construtor da classe Foo e de todos os construtores das subclasses que herdem de Foo. pointcut allnew(): Exemplo 45 – Utilização do símbolo “+”. call ( Foo+.new() ) No exemplo 46, é utilizado os símbolos “*”, “+” e (..), para seleccionar todas as chamadas aos métodos que cujo nome comece por “set” e com um número arbitrário de argumentos de entrada da classe Figura e todas as subclasses que herdem dessa classe. pointcut allnew(): call (* Figura+.set*(..) ) Exemplo 46 – Utilização do símbolo “+”, “*” “(..)”. 6.3.9 Modificadores É possível capturar métodos com base nos seus modificadores, public, static, para definir cláusulas mais específicas (exemplo 47). call(public final void C.foo() throws ArrayOutOfBoundsException) Exemplo 47 – utilização do modificador public. 106 Também é possível utilizar o símbolo booleano da inversão (!) para definir join points (exemplo 48). call(!static * *(..)) Exemplo 48 – utilização do modificador static em conjunto com a negação. 6.3.10 Estrutura Para definir join points em relação a estrutura dentro de uma classe, utiliza-se o operador within para capturar join points dentro do corpo de uma classe e declarações associadas a esse tipo de classe (exemplo 49). within( Point ) Exemplo 49 – Operador Within. Outro operador withincode permite capturar join points no corpo de um método de uma classe fornecendo a classe e o método como assinatura (exemplo 50). withincode( void Device.update() ) Exemplo 50 – Operador withincode. 6.3.11 Fluxo O operador clfow (exemplo 51) permite capturar todos os join points a partir de um determinado ponto, caso se queira excluir o join point inicial deve-se usar o operador cflowbelow (exemplo 52). cflow ( execution ( void device.update() ) ) Exemplo 51 – Operador cflow. cflowbelow( execution ( void device.update() ) ) Exemplo 52 - Operador cflowbelow. 107 6.4 ADVICE Advices permitem definir um bloco de código que é associado a um pointcut. O bloco de código é o comportamento adicional onde é implementado o crosscutting concerns. Advices podem ser executados ‘antes’, ‘depois’ ou ‘ao invés de’, permitindo definir o tempo em que é aplicado o comportamento ao join point. Advices estão associados a um pointcut, este pode estar previamente definido ou pode ser declarado anonimamente (exemplo 53). TipoDeAdvice [ Excepção ] : Pointcut { Bloco de codigo } Exemplo 53 – Sintaxe de declaração de um Advice. 6.4.1 Tipos de Advice Para definir um comportamento que é efectuado antes de um join point define-se o tipo de advice como before, na interface do advice devem ser colocados os argumentos que permitem ter acesso ao contexto do join point capturado (exemplo 54). before ( arg1, …, argN ) Exemplo 54 – Advice tipo antes. Para definir um comportamento depois de uma chamada a um método tem de se ter em conta que existem várias possibilidades para a forma de um método estar declarado. Este pode ser declarado para lançar uma excepção, devolver um objecto ou nenhuma das duas anteriores. Para definir um comportamento depois de uma chamada a um método e este devolver um argumento de saída (exemplo 55). after (arg1, …, argN) returning [ (arg1, …, argN) ] Exemplo 55 – Advice tipo depois, com retorno de argumentos. 108 Para definir um comportamento depois de uma chamada a um método, e este método lançar uma excepção (exemplo 56). after (arg1, …, argN) throwing [ (arg1, …, argN) ] Exemplo 56 – Advice tipo depois com declaração de excepção. Para definir um comportamento depois de uma chamada a um método (exemplo 57). after (arg1, …, argN) Exemplo 57 – Advice tipo depois. É possível substituir o bloco de código que é executado num determinado join point para definir esse comportamento deve ser usado o tipo around (exemplo 58). Tipo around (arg1, …, argN) Exemplo 58 – Advice tipo invés de. 6.4.2 Exemplo de advice No exemplo 59 é definido um pointcut que captura chamadas aos métodos setP1 e setP2 da classe Line. São implementados dois advices, um que executa antes e outro que executa depois. Por exemplo com este dois tipos de advices é simples implementar um sistema de anular e repor estados. pointcut chamada(): call(void Line.setP1(..))|| call(void ; before(): chamada(){ System.out.println("antes"); } after(): chamada(){ System.out.println("depois"); } Exemplo 59 – Exemplo da declaração de advices Line.setP2(..)) 109 6.4.3 Lançar excepções Excepções podem ser, opcionalmente, lançadas no bloco de código de um advice, mas existem regras bem definidas para quando é possível lançar excepções e quando não é: • Execução ou chamadas a métodos ou construtores - só é possível lançar as excepções que estão declaradas no(s) método(s) ou construtor(es) em causa; • Atribuição e leitura de atributos e inicialização estática - não é possível lançar qualquer excepção; • Tratamento de excepções - as excepções que são possíveis lançar são do mesmo tipo que a capturada; • Pré-inicialização e inicialização - é possível lançar as excepções que estão declaradas no construtor da classe que está a ser inicializada; • 6.4.4 Execução de advice - qualquer excepção que está declarada na clausula do advice. Precedência Podem existir casos em que múltiplos advices estão aplicados a um mesmo join point, a ordem pela qual são aplicados pode ser definida pelo programador. Existem dois casos distintos: 1. Os advices estão em unidades modulares, aspectos, diferentes; 2. Os advices estão na mesma unidade modular. Se dois advices, A e B, estão aplicados a um mesmo join point mas estes advices encontram-se em aspectos diferentes, a precedência tem as seguintes condições: 110 • Se o aspecto A é invocado antes que o aspecto B, especificando este comportamento com o operador declare precedence, então todos os advices aplicados ao mesmo join point no aspecto A, tem precedência aos do aspecto B; • Se o aspecto A estende o aspecto de B, então todos os advices definidos no aspecto A tem precedência sobre os advices do aspecto B, a não ser que esteja especificado de outra forma, com o operador declare precedence por exemplo; • Se não existir qualquer forma de declaração por parte do programador, então não é possível determinar qual dos aspectos tem precedência. Se dois advices aplicados a um mesmo join point se encontram declarados no mesmo aspecto, a precedência tem as seguintes condições: • Se o advice é do tipo after, o advice que aparecer em último lugar no código do aspecto terá precedência; • Para qualquer outro tipo de advice, o que aparecer em primeiro lugar no código do aspecto terá precedência. Sintaxe para a definição explícita de precedência de aspectos (exemplo 60). declare precedence : Lista de Aspectos; Exemplo 60 – Sintaxe da precedência de aspectos. No exemplo 61, é declarada a precedência de alguns aspectos, de notar que se pode utilizar Wildcards declare precedence: *..*Security*, Logging+, *; Exemplo 61 – Precedência de aspectos. 111 No exemplo 62, são definidos três aspectos. O aspecto Ordem define a ordem em que os outros dois aspectos são executados, já que esses dois capturam o mesmo ponto no fluxo do programa. aspect Ordem { declare precedence : CountEntry, DisallowNulls; } aspect DisallowNulls { pointcut allTypeMethods(Line obj): call(* *(..)) && args(obj, ..); before(Line obj): allTypeMethods(obj) { if (obj == null) throw new RuntimeException(); } } aspect CountEntry { pointcut allTypeMethods(Line obj): call(* *(..)) && args(obj, ..); static int count = 0; before(): allTypeMethods(Line) { count++; } } Exemplo 62 – Declaração de precedências. 6.5 INTER-TYPE DECLARATIONS Inter-type declarations, permitem alterar o comportamento estático do programa, a estrutura do código, como a introdução de atributos, métodos em classes e interfaces. Permitindo a modificação da hierarquia, a declaração de erros, advertências de compilação e enfraquecimento de excepções (secção 6.6). 112 6.5.1 Adição comportamentos AspectJ permite a introdução de novos atributos e métodos em classes e interfaces, e construtores em classes. A adição modular de comportamentos, evita redundância de código, e permite a implementação de comportamentos por omissão em interfaces, que posteriormente são compostos nas classes que as implementam. Adicionar Métodos. É possível adicionar métodos e declarar o seu bloco de código (exemplo 63) ou métodos abstractos (exemplo 64). [ Modificadores ] Tipo NomeClasse . nomeMetodo( argumentos ) [ Excepção ] { Código } Exemplo 63 – Adição de um método. abstract [Modificadores] Tipo NomeClasse. nomeMetodo ( argumentos ) [Excepção] ; Exemplo 64 – Adição de um método abstracto. No exemplo 65 é adicionado um método a um interface. //definir a interface interface Iface {} aspect A { private void Iface.m() { System.err.println("metodo da interface"); } void worksOnI(Iface iface) { // invocar o método iface.m(); } m. Exemplo 65 – Exemplificação da adição de métodos. Adição de construtores A adição de construtores têm sintaxe semelhante à adição de métodos mas segue a regra que o nome tem de ser o termo new para a declaração de um construtor (exemplo 66). 113 [Modificadores] NomeClasse. new (argumentos) [Excepção] { Código } Exemplo 66 – Adição de um construtor a uma classe. Adição de atributos È possível também adicionar atributos às classes, a sintaxe para a declaração de atributos permite a atribuição de valores (exemplo 67) ou só a declaração do atributo (exemplo 68). [Modificadores] Type NomeClasse. nomeAtributo = Expressão; Exemplo 67 – Declaração de um atributo com atribuição de valor. [Modificadores] Type NomeClasse. nomeAtributo; Exemplo 68 – Declaração de um atributo. Modificar hierarquia AspectJ permite modificar a hierarquia das classes (exemplo 69), desde que esta não viole as regras de herança do Java, e declarar interfaces (exemplo 70). declare parents: NomeClasse extends TipoaEstender; Exemplo 69 – Estender um tipo. declare parents: NomeClasse implements TipoaEmplementar; Exemplo 70 – Implementar um tipo. No exemplo 71, o aspecto modifica a classe estendendo a hierarquia desta, implementado a interface runnable e adicionando os métodos que são obrigatórios implementar dessa interface “run”. aspect A { declare parents: SomeClass implements Runnable; public void SomeClass.run() { ... } } Exemplo 71 – Estender e implementar métodos em classes. 114 Declaração de erros e advertências em tempo de compilação É possível declarar erros e advertências em tempo de compilação. Quando se verifica um determinado comportamento definido num pointcut, o compilador avisa com um erro ou com uma advertência, emitindo uma mensagem que foi posteriormente definida pelo programador. O pointcut pode estar previamente definido ou declarado anonimamente. A sintaxe para declaração de erros é a do exemplo 72, enquanto a sintaxe para a declaração de advertências é a definida no exemplo 73. declare error: Pointcut: "Mensagem de erro" Exemplo 72 – Atribuição de um erro a um pointcut. declare warning: Pointcut: "mensagem de aviso" Exemplo 73 – Atribuição de uma advertência a um pointcut. No exemplo 74, define-se um erro para obrigar o programador a utilizar os métodos “set” dos atributos, e fornecendo um erro quando este não o faz. Assim quando dentro da classe se aceder directamente para escrever num atributo, o compilador ao compilar o programa emite um erro. declare error: set(int Point.*) && !withincode(void Point.set*(int)): "use o método setter, mesmo dentro da classe."; Exemplo 74 – exemplo da utilização da declaração de erros. 6.6 ENFRAQUECIMENTO DE EXCEPÇÕES Podem existir situações onde é obrigatório tratar excepções, mas essas excepções da forma que o código é implementado nunca irão ser despoltadas. O Java obriga a que seja declarado o tratamento da excepção, excepto se essa excepção estender a excepção 115 ‘RuntimeException‘ou ‘Error’. AspectJ permite redefinir excepções da linguagem Java para que estas herdem da excepção RuntimeException, e assim evitar o tratamento da excepção (exemplo 75). declare soft: TipoExcepção: Pointcut; Exemplo 75 – Sintaxe para a declaração de enfraquecimento de excepções. No exemplo 76, é utilizado o enfraquecimento de excepção para não tratar a excepção ClassNotFoundException. abstract pointcut softeningPC(); before() : softeningPC() { Class.forName("FooClass"); // erro: ClassNotFoundException } declare soft : ClassNotFoundException : call(* Class.*(..)); Exemplo 76 - Exemplo do enfraquecimento de excepções. 6.7 ASPECTOS Aspectos são unidades modulares que contêm a implementação dos comportamentos transversais, são compostos por pointcuts, advices, inter-type declarations e código java normal. Aspectos não podem ser instanciados directamente, mas se for necessário é possível criar um construtor, mas este construtor não pode ter argumentos ou lançar excepções. Para se declarar um aspecto utiliza-se o termo aspect (exemplo 77). aspect Nome { ... } Exemplo 77 – Declaração de um aspecto. 116 É possível que aspectos contenham outros aspectos internamente, estes aspectos que estão acoplados dentro de outros aspectos têm de ser obrigatoriamente declarados estáticos com o modificador static. Aspectos podem ser estendidos por outros aspectos da mesma forma que uma classe normal de Java, mas só se o aspecto base está declarado como abstracto. Aspectos podem estender classes ou interfaces, embora classes não possam estender aspectos. Aspectos não podem ser instanciados directamente, mas é possível aceder à sua instância através do método estático, aspectOf, que está implementado em todos os aspectos concretos. 6.7.1 Aspectos Singleton Por omissão o aspecto só tem uma instância, que é transversal ao programa que está a ser executado, a este tipo de aspecto chama-se singleton. A sintaxe, normal, para a declaração de um aspecto singleton é a do exemplo 78. Opcionalmente pode-se usar a definição do exemplo 79. aspect Nome { ... } Exemplo 78 – Declaração de um aspecto singleton. aspect Nome issingleton() { ... } Exemplo 79 - Declaração alternativa de um aspecto singleton. 6.7.2 Instanciar aspectos por objecto É possível associar um aspecto a um objecto concreto, e os advices pertencentes a esse aspecto só são aplicados a esse objecto. O aspecto é declarado e associado com um pointcut e, é 117 criado uma instância do aspecto por cada objecto que o pointcut intercepta. É possível definir comportamento estático (exemplo 80) ou dinâmico (exemplo 81). aspect Id perthis(Pointcut) { ... } Exemplo 80 – Instancia de aspectos por objectos dinamicamente. aspect Id pertarget(Pointcut) { ... } Exemplo 81 - Instancia de aspectos por objectos estaticamente. 6.7.3 Aspectos por fluxo. É possível criar um aspecto por cada fluxo do programa a partir de um determinado joinpoint. O aspecto é associado com um pointcut. Quando se verificar esse poincut são instanciados um aspecto por cada fluxo diferente que se crie a partir desse fluxo. Sintaxe para a declaração a partir de um determinado fluxo (exemplo 82). aspect NomeAspecto percflow(Pointcut) { ... } Exemplo 82 – Definir aspecto por fluxo. É possível omitir o primeiro fluxo que ocorre no joinpoint (exemplo 83). aspect NomeAspecto percflowbelow(Pointcut) { ... } Exemplo 83 – Definir aspecto por fluxo, excepto o inicial. 6.7.4 Aspectos com privilégios Para possibilitar o acesso aos vários tipos de modificadores, em especial a métodos e atributos declarados como private, é possível declarar aspectos privilegiados (exemplo 84). privileged aspect Id { ... } Exemplo 84 – definir aspecto com privilégios. 118 No exemplo 85, é demonstrado o acesso de um aspecto a atributos declarados private. class C { private int i = 0; void incI(int x) { i = i+x; } } privileged aspect A { static final int MAX = 1000; before(int x, C c): call(void C.incI(int)) && target(c) && args(x) { if (c.i+x > MAX) throw new RuntimeException(); } } Exemplo 85 – Exemplo de Aspecto privilegiado. 119 7. ANEXO 2: @ASPECTJ 7.1 @APECTJ Na versão 1.5 da linguagem de programação AspectJ foi introduzido um novo estilo de declarar aspectos, advices e pointcuts, mas mantendo a mesma semântica. A este estilo de desenvolvimento foi chamado @aspectJ35, este estilo é semelhante às anotações do Java 5 e parecido à aplicação de atributos da Framework .NET. Este estilo permite desenvolver aspectos como se de classes normais de Java se tratassem, que depois são anotadas, utilizando a semântica do AspectJ. Os métodos dessas classes quando anotados tornam-se Advices. Este estilo permite que se possa desenvolver programas POA sem alterar as ferramentas de desenvolvimento, mas é necessário incorporar manualmente o processo de Weaving no processo de compilação do projecto, através, por exemplo, de tarefas ANT36. Se não existir integração do processo de compilação o projecto, o programa compilado tem o comportamento normal POO. 35 36 http://www.eclipse.org/aspectj/doc/released/adk15notebook/ataspectj.html (Acedido em Junho de 2008) http://ant.apache.org/resources.html (Acedido em Junho 2008) 120 Antes de se poder desenvolver programas POA neste estilo, é necessário primeiro instalar a linguagem AspectJ37 e importar as bibliotecas, ficheiros jar, ao projecto Java. 7.2 DECLARAÇÃO DE ASPECTOS Para declarar um aspecto, é necessário primeiro criar uma classe. Nesta classe é necessário importar a anotação org.aspectj.lang.annotation.Aspect, com esta importação é possível anotar a classe com a palavra “@aspect” por cima do nome da (exemplo 86). A ferramenta de desenvolvimento, IDE, mesmo sem alterações reconhece a anotação, devido a importação da anotação, e consegue detectar erros de sintaxe. import org.aspectj.lang.annotation.Aspect; @Aspect public class NotVeryUsefulAspect { } Exemplo 86 - declarar um aspecto em @AspectJ. 7.3 DECLARAÇÃO DE POINTCUTS Para declarar um pointcut, primeiro importa-se a anotação org.aspectj.lang.annotation.Pointcut, que permite ao IDE reconhecer a anotação e ao programador utilizar a mesma. Dentro da classe anotada como aspecto declara-se um método como o bloco de código em branco. Anota-se esse método com a palavra “@Pointcut” e as 37 http://www.eclipse.org/aspectj/downloads.php (Acedido em Junho 2008) 121 condições do pointcut. A semântica é igual ao estilo previamente descrito no anexo 1 como é possível verificar no exemplo 87. import org.aspectj.lang.annotation.Pointcut; .. @Pointcut("execution(* transfer(..))")// comportamento private void anyOldTransfer() {}// assinatura Exemplo 87 – Declaração de um Pointcut em @AspectJ. É possível combinar vários pointcuts num só utilizando a lógica booleana (exemplo 88). @Pointcut("execution(public * *(..))") private void anyPublicOperation() {} @Pointcut("within(com.xyz.someapp.trading*") private void inTrading() {} @Pointcut("anyPublicOperation() && inTrading()") private void tradingOperation() {} Exemplo 88 – junção de pointcuts em @AspectJ. 7.4 DECLARAÇÃO DE ADVICES Neste estilo os advices são implementados através de métodos, os quais são anotados com a devida sintaxe. Para definir advices primeiro é necessário importar as anotações referentes ao tipo de comportamento pretendido, antes (.Before), depois (.After, .AfterReturning .AfterThrowing) ou invés de (.Around). Para anotar o método que vai implementar o comportamento transversal, utiliza-se a sintaxe já previamente introduzida (exemplo 89). import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeExample { ou 122 @Before("call(* dataAccessOperation(..))") public void doAccessCheck() { // ... } } Exemplo 89 – criação de um advice do tipo antes em @AspectJ. Para definir um advice que seja invocado depois (After), a forma de declaração é idêntica aos passos anteriores. Implementa-se o método que irá ser invocado ‘depois’ e anota-se as condições para dar ocorrência do comportamento e não esquecendo importar primeiro a anotação (exemplo 90). import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } } Exemplo 90 - criação de um advice do tipo depois em @AspectJ Também é possível aceder ao argumento de saída (returning) pelo método alvo depois da sua execução (exemplo 91). import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal") public void doAccessCheck(Object retVal) { // ... 123 } } Exemplo 91 - criação de um advice do tipo depois em @AspectJ com acesso a contexto. É possível utilizar o tipo de advice ao invés de (Around) que permite por exemplo executar código invés do código do método alvo. No exemplo 92, é utilizado o tipo de advice ao invés de, para executar tarefas antes do código do método alvo executar, efectuar a execução do código do método alvo (proceed), e no fim executar outras tarefas. import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect public class AroundExample { @Around("call (* businessService())") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // iniciar contador Object retVal = pjp.proceed(); // parar contador return retVal; } } Exemplo 92 - criação de um advice do tipo invés em @AspectJ 7.5 INTER-TYPE DECLARATIONS Inter-type declarations neste estilo sofre de algumas limitações pois não é possível introduzir atributos e construtores, já que não é possível invocá-los antes de estes estarem tricotados com o resto do código. A implementação recorre a Interfaces, a estrutura que se pretende introduzir é definida em classes e interfaces, e os métodos e atributos dessas classes e interfaces são introduzidos no programa definindo aspectos anotados. 124 Nos exemplos 93,94 e 95 é mostrado como se procede a alteração da estrutura de uma classe, primeiro é necessário criar uma interface (exemplo 93). Em seguido é necessário criar uma implementação da interface (exemplo 94). Cria-se um aspecto onde é necessário definir qual a classe que vai ser alterada, fornecendo a classe que implementa a interface (exemplo 95). // Interface public interface Moody { Mood getMood(); }; Exemplo 93 – Interface que contem os métodos ou atributos a ser compostos. // Implementação da Interface public static class MoodyImpl implements Moody { private Mood mood = Mood.HAPPY; public Mood getMood() { return mood; } } Exemplo 94 – Implementação da interface do exemplo 97. //Aspecto que compõem a interface @Aspect public class MoodIndicator { // Introduzir a interface em classes @DeclareParents(value="org.xzy..*",defaultImpl=MoodyImpl.class) private Moody implementedInterface; //Invocar a implementação da interface @Before("execution(* *.*(..)) && this(m)") void feelingMoody(Moody m) { System.out.println("I'm feeling " + m.getMood()); } } Exemplo 95 – Aspecto para modificar a estrutura de uma classe 125 8. ANEXO 3: IDES 8.1 FERRAMENTAS DE DESENVOLVIMENTO A linguagem de programação orientada a aspectos AspectJ pode ser adicionada a qualquer projecto em Java como uma biblioteca. São disponibilizados por essa biblioteca um conjunto de ferramentas para a compilação, depuração e documentação, alternativamente pode estar incluída num plug-in para IDE, como o plug-in AJDT para Eclipse. Este Plug-in para além de adicionar o suporte para a linguagem de programação facilita o seu desenvolvimento com ajudas visuais. 8.2 PLUG-IN AJDT O Plug-in AJDT38 [Clement, Colyer et al. 2003] para a plataforma Eclipse, adiciona à plataforma de desenvolvimento o suporte necessário para o desenvolvimento orientado a aspectos utilizando a linguagem AspectJ, e um conjunto de ferramentas visuais, para ajudar o programador no desenvolvimento de aspectos. 38 http://www.eclipse.org/ajdt/ (Acedido em Julho de 2008) 126 AJDT possui a vista cross reference cujas indicações visuais permitem visualmente perceber a interacção dos advices com o programa. A perspectiva visualizer que permite ver o efeito dos aspectos no sistema, e o depurador que permite depurar passo a passo programas AspectJ [Clement, Colyer et al. 2003]. Na figura 24, pode-se observar a perspectiva Java, do lado direito o package Explorer que contem os ficheiros java ordenados por package. Ao centro o editor dos ficheiros java, e do lado esquerdo a vista outline e debaixo desta a vista cross reference. De notar do lado esquerdo do editor existem pequenos ícones que denotam informação sobre advices aplicados. Figura 24 – Eclipse, perspectiva Java Nas figuras 25, 26 apresenta-se a perspectiva visualizer, que permite ter a percepção global de como os vários aspectos afectam o sistema. Como se pode verificar cada aspecto tem 127 uma cor diferente, que é utilizada para indicar os pontos no sistema onde estão esses aspectos aplicados. Figura 25 - Eclipse, Perspectiva visualiser Figura 26 – Detalhe visualiser. A vista cross reference (figura 27) permite visualizar onde os advices do aspecto estão a ser aplicados. Os nomes por baixo de advices são navegáveis, o utilizador ao carregar num dos 128 nomes listados é levado para o editor de texto na linha onde é aplicado o advice. Caso se esteja a editar uma classe que contenha locais onde são aplicados advices, na vista cross reference são mostrados esses advices, e clicando nos advices ao utilizador é mostrado o ficheiro na janela do editor de texto e na linha que contem esse advice. Figura 27 – Detalhe de cross references. A depuração de programas AspectJ está integrada na plataforma, a perspectiva de depuração, figura 28, permite depurar passo a passo aspectos e código normal java. Figura 28 – Perspectiva de depuração. 129 FERRAMENTAS STANDALONE DA BIBLIOTECA ASPECTJ 8.3 As ferramentas incluídas com a biblioteca normal da linguagem de programação AspectJ são: • Ajc: o compilador (weaver); • Ajdoc: ferramenta para gerar a documentação; • Ajbrowser: para visualizar a interligação dos aspectos com o sistema de software; Todos os parâmetros e configurações podem ser consultados online na página de ajuda do projecto AspectJ3940 39 40 http://www.eclipse.org/aspectj/ (Acedido em Junho 2008) http://www.eclipse.org/aspectj/doc/released/devguide/command-line-tools.html (Acedido em Junho 2008) 130 9. ANEXO 4: POSTSHARP 9.1 INTERCEPÇÃO DE CHAMADAS A MÉTODOS A classe OnMethodBoundaryAspect, permite interceptar chamadas a métodos e definir comportamentos a entrada, saída, em caso de sucesso ou em caso que seja lançada uma excepção pelo método a que é aplicado o atributo. Este comportamento só é valido para os métodos dentro do contexto do tipo (classe) ao qual é aplicado o atributo. Os métodos da classe que permitem definir advices são apresentados na tabela 11. Método OnEntry OnSuccess OnException OnExit Tipo de Advice Entrada no método alvo. Execução do método alvo com sucesso. Método alvo lança uma excepção. Saída do método alvo. Tabela 11 – Métodos da classe OnMethodBoundaryAspect. No exemplo 96, o atributo Trace estende a classe OnMethodBoundaryAspect e reimplementa os quatro métodos que definem os tipos de advice. class TraceAttribute: OnMethodBoundaryAspect { public override void OnEntry(MethodExecutionEventArgs args) { //entrada no metodo } public override void OnExit(MethodExecutionEventArgs args) { 131 //saida do metodo } public override void OnSuccess(MethodExecutionEventArgs args) { //metodo executado com sucesso } public override void OnException(MethodExecutionEventArgs args) { //metodo lançõu uma excepção } } Exemplo 96 – Exemplo de um atributo Trace. 9.2 INTERCEPTAR INVOCAÇÃO A MÉTODOS A classe OnMethodInvocationAspect, contem um método que permite receber notificações de invocações a métodos que não estão no contexto da assembly, em que o atributo está associado. O nome do método do advice é OnInvocation. Os atributos podem ser aplicados no local onde os métodos são invocados. O único comportamento permitido é quando o método é invocado, isto é antes de o método alvo ser executado. No exemplo 97, mostra-se o código necessário para estender a classe. Este atributo deve ser aplicado a uma classe ou método que contenha a invocação ao método alvo. class TraceAttribute: OnMethodInvocationAspect { public override void OnInvocation(MethodInvocationEventArgs args) { //metodo foi invocado } } Exemplo 97 – Exemplo de um atributo para invocação de métodos 132 9.3 INTERCEPTAR TRATAMENTO DE EXCEPÇÕES A classe OnExceptionAspect, fornece o advice que permite receber notificações no caso de lançamento de excepções. O método que define o advice é OnException (exemplo 98). class TraceAttribute: OnExceptionAspect { public override void OnException(MethodExecutionEventArgs args) { //Tratar a excepção } } Exemplo 98 - Exemplo de um atributo para tratar excepções. 9.4 INTERCEPTAR ACESSO A VARIÁVEIS Os métodos da classe OnFieldAccessAspect permitem interceptar acessos de leitura e escrita a variáveis. Os métodos que definem os advices são apresentados na tabela 12. Metodo OnGetValue SetValue Descrição do advice Notificação de quando o valor da variável é lido. Notificação de quando o valor da variável é escrito. Tabela 12 – Métodos da classe OnFieldAccessAspect No exemplo 99, são implementados pelo atributo os métodos para a notificação da escrita e do acesso de leitura. O atributo deve ser atribuído a uma classe. class TraceAttribute: OnFieldAccessAspect { public override void OnGetValue(FieldAccessEventArgs eventArgs) { //variavel foi acedida } public override void OnSetValue(FieldAccessEventArgs eventArgs) { //novo valor atribuido à variavel 133 } } Exemplo 99 – exemplo de atributo de acesso a variáveis. 9.5 IMPLEMENTAÇÃO DE MÉTODOS A classe ImplementMethodAspect, permite implementar métodos abstractos ou extern, deferindo a execução desses métodos para o método OnExecution. O bloco de código que é executado é o que está definido no atributo. Se o atributo é aplicado a um método com um bloco de código, este código não irá ser executado. O comportamento é semelhante ao invés dos advices de AspectJ (exemplo 100). class TraceAttribute: ImplementMethodAspect { public override void OnExecution(MethodExecutionEventArgs args) { //implementação do método } } Exemplo 100 – exemplo de um atributo para implementar um método. 9.6 IMPLEMENTAR E COMPOR INTERFACES A classe CompositionAspect, permite implementar uma interface e por composição aplica-la a numa classe. É necessário implementar a interface numa classe, separadamente, e em runtime devolver um objecto com a implementação da interface no método CreateImplementationObject. O PostSharp necessita que em tempo de execução lhe seja devolvido o tipo da interface que é implementada através do método GetPublicInterface. 134 No exemplo 101, são mostrados os dois métodos que são preciso implementar. class TraceAttribute : CompositionAspect { public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs) { //devolver objecto contendo a implementação da interface } public override Type GetPublicInterface(Type containerType) { //devolver que interface que é implementada } } Exemplo 101 – exemplo de atributo para compor interfaces. 9.7 APLICAÇÃO DOS ATRIBUTOS A aplicação dos atributos é efectuada de igual forma que os atributos normais da plataforma .NET, não existindo diferenças na atribuição dos atributos definidos pelos programadores. No exemplo 102, o atributo Trace é aplicado a uma classe, se por exemplo o aspecto implementado pelo atributo é do tipo chamadas a métodos, o aspecto captura todos os métodos desta classe. [Trace] class Program Exemplo 102 – Aplicar atributo a uma classe. No exemplo 103, o atributo é aplicado a um só método [Trace] static void sayHello(string name) { Console.WriteLine("Hello, {0}.", name); } Exemplo 103 – Aplicar atributo a um método. No exemplo 104, o atributo é aplicado a todos os métodos da classe, mas só aqueles que comecem pelas letras ‘say’. [Trace(AttributeTargetMembers="say*")] 135 class Program Exemplo 104 – Aplicar atributo definindo a quais métodos. 9.8 ATRIBUIÇÃO A MÚLTIPLOS TIPOS. O PostSharp permite a atribuição de um atributo a múltiplos tipos, classes por exemplo, no exemplo 105, o atributo Log é aplicado a todo o Namespace BusinessObjects [assembly: Log(AttributeTargetTypes = " MyApp . BusinessObjects .*", AttributeTargetMemberAttributes = MulticastAttributes.Public)] Exemplo 105 – Atribuição de um atributo a múltiplos tipos. 9.9 EXEMPLO DE UTILIZAÇÃO Para ilustrar uma utilização do PostSharp LAOS, desenvolvemos um projecto, que de forma simples permite imprimir uma mensagem antes e depois de um determinado método ser invocado, este tipo de comportamento é útil para fazer registos, segurança, transacções, anular e repor estados de objectos. Este exemplo está disponível em vídeo41 na página oficial do PostSharp42. 1. No Visual Studio 2005 cria-se um projecto C# do tipo Console Application (figura 29) . 41 42 http://www.postsharp.org/about/video/default.aspx (Acedido em Junho 2008) http://www.postsharp.org/ (Acedido em Junho 2008) 136 Figura 29 – Visual Studio criar Projecto 2. Importar a referência das assemblies PostSharp.public e PostSharp.Laos para o projecto (figura 30). Ficando estas referencias associadas ao projecto (figura 31). Figura 30 - Visual Studio importar Referencias. 137 Figura 31 - Visual Studio, referencias no projecto. 3. Criar os atributos estendendo as classes fornecidas pelo Namespace PostSharp.Laos. Criando uma nova Classe, e estender essa classe da classe OnMethodBoundrayAspect e implementar os métodos necessários, no exemplo 106 são os métodos onEntry e onExit. No exemplo também se utiliza o método CompileTimeInitialize para se obter o nome do método logo na fase de compilação evitando assim que em tempo de execução se tenha de calcular o nome do método. using System; using System.Collections.Generic; using System.Text; using PostSharp.Laos; namespace TraceSample { [Serializable] class TraceAttribute: OnMethodBoundaryAspect { string methodName; public override void CompileTimeInitialize(System.Reflection.MethodBase method) { this.methodName = method.DeclaringType.Name + "/" + method.Name; } public override void OnEntry(MethodExecutionEventArgs eventArgs) { Console.WriteLine("On entry of {0}.", this.methodName); } public override void OnExit(MethodExecutionEventArgs eventArgs) 138 { Console.WriteLine("On exit of {0}.", this.methodName); } } } Exemplo 106 – Exemplo prático de um Atributo desenvolvido em PostSharp Laos. 4. Criados os atributos, estes agora precisam de ser aplicados aos métodos, existem várias formas de o fazer, no exemplo 107 é aplicado o atributo à classe e definido que só deve ser aos métodos dessa classe que comecem pelas palavras “say”. using System; using System.Collections.Generic; using System.Text; namespace TraceSample { [Trace(AttributeTargetMembers="say*")] class Program { static void Main(string[] args) { sayHello("World"); sayGoodbye("World"); Console.ReadKey(); } static void sayGoodbye(string name) { Console.WriteLine("Good bye, {0}.", name); } static void sayHello(string name) { Console.WriteLine("Hello, {0}.", name); } } } Exemplo 107 – Exemplo prático da aplicação de um Atributo. 5. Completada a implementação do código efectua-se a compilação. O processo de compilação do PostSharp é automaticamente inserido a quando da instalação no 139 processo de compilação do visual Studio 2005. O programador quando compila o programa pode verificar no output o resultado da compilação, exemplo 108. ------ Rebuild All started: Project: TraceSample, Configuration: Debug Any CPU ……………………………………………. Compile complete -- 0 errors, 0 warnings PostSharp 1.0 [1.0.8.316] - Copyright (c) Gael Fraiteur, 2005-2008. TraceSample -> C:\...\bin\Debug\TraceSample.exe ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ========== Exemplo 108 – Resultado do Weaving. 6. Completada a compilação o resultado da execução do programa é mostrado na figura 32. Figura 32 – Resultado do programa de exemplo. 140 ANEXO 5: FRAMEWORKS Para além de linguagens de programação orientada a aspectos, existem sistemas, frameworks, que implementam o paradigma da programação orientada a aspectos de modo a facilitar a adição modular de funcionalidades em aplicações. 9.9.1 Spring Spring43 é uma Framework JAVA/J2EE para o desenvolvimento de aplicações empresariais, permitindo modularmente configurar transacções, acesso remoto usando RMI ou Web services, acesso a dados. Spring suporta o paradigma orientado a aspectos que permite por exemplo gerir transacções, complementando POO com AOP. Spring Framework suporta o crosscutting de métodos embora seja possível adicionar bibliotecas do AspectJ para estender as possibilidades de crosscutting. A POA presente na Framework Spring está orientada para a intercepção de métodos, antes, depois ou ao invés de, embora a integração com o AspectJ permita expandir o tipo de Join Points. 43 http://www.springframework.org/ (Acedido em Junho 2008) 141 A implementação do paradigma é com base em proxys dinâmicas, e permite definir aspectos através de anotações @AspectJ. 9.9.2 Jboss Jboss44 é outra framework para o desenvolvimento de aplicações midleware que permite o utilização de AOP para o aumento da modularização, proporcionando uma melhor separação entre a lógica da aplicação e o código do sistema. Jboss utiliza o estilo anotado @Aspectj para a definição de aspectos45. Os aspectos em Jboss são dinâmicos, podem ser activados e desactivados durante a execução do programa. 44 45 http://www.jboss.org/jbossaop/ (Acedido em Junho 2008) http://www.jboss.org/feeds/post/dynamic_aop_tutorial_part_1 (Acedido em Junho 2008) 142 ANEXO 6: EXEMPLO OBSERVER PATTERN. No exemplo 109 ao 115 é apresentado uma possível implementação de um padrão de desenho, o exemplo 109 é um aspecto abstracto que implementa o comportamento comum do padrão de desenho Observer em AspectJ. Ser abstracto permite a outros aspectos estender e reutilizar o comportamento comum e implementar só o comportamento específico a esse aspecto. public abstract aspect ObserverProtocol { protected interface Subject { } protected interface Observer { } private WeakHashMap perSubjectObservers; protected List<Observer> getObservers(Subject s) { if (perSubjectObservers == null) { perSubjectObservers = new WeakHashMap(); } List observers = (List) perSubjectObservers.get(s); if (observers == null) { observers = new LinkedList<Observer>(); perSubjectObservers.put(s, observers); } return observers; } public void addObserver(Subject s, Observer o) { getObservers(s).add(o); } public void removeObserver(Subject s, Observer o) { getObservers(s).remove(o); } abstract protected pointcut subjectChange(Subject s); abstract protected void updateObserver(Subject s, Observer o); after(Subject s): subjectChange(s) { Iterator iter = getObservers(s).iterator(); while (iter.hasNext()) { updateObserver(s, ((Observer) iter.next())); } 143 } } Exemplo 109 – Observer Protocol, Aspecto Abstracto. No exemplo 110 é estendido o padrão de desenho abstracto do exemplo 92, ObserverProtocol. Definem-se quais as classes participantes no padrão e qual é o comportamento que o padrão de desenho tem. public aspect CoordinateObserver extends ObserverProtocol { declare parents: Point implements Subject; declare parents: Line implements Subject; declare parents: Screen implements Observer; protected pointcut subjectChange(Subject s): (call(void Point.setX(int)) || call(void Point.setY(int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) ) && target(s); protected void updateObserver(Subject s, Observer o) { ((Screen) o).display("Coordinate change."); } } Exemplo 110 – CoordinateObserver, Aspecto Concreto. No exemplo 111 é estendido novamente o padrão de desenho abstracto, ObserverProtocol. Definem-se quais as classes participantes no padrão e qual é o comportamento que o padrão de desenho tem. Como é possível verificar implementou-se um novo padrão de desenho com um comportamento diferente do exemplo 93. public aspect ColorObserver extends ObserverProtocol { declare parents: Point implements Subject; declare parents: Line implements Subject; declare parents: Screen implements Observer; protected pointcut subjectChange(Subject s): (call(void Point.setColor(Color)) || call(void Line.setColor(Color)) ) && target(s); 144 protected void updateObserver(Subject s, Observer o) { ((Screen) o).display("Color change."); } } Exemplo 111 – ColoObserver, aspecto concreto. No exemplo 112 a linha de acção é igual aos dois exemplos anteriores, estender o padrão abstracto, definir as classes participantes e implementar o comportamento em advices. public aspect ScreenObserver extends ObserverProtocol { declare parents: Screen implements Subject; declare parents: Screen implements Observer; protected pointcut subjectChange(Subject s): call(void Screen.display(String)) && target(s); protected void updateObserver(Subject s, Observer o) { ((Screen) o).display("Screen updated."); } } Exemplo 112 – ScreenObserver, aspecto concreto. Nos exemplos seguintes 113, 114 e 115 é apresentada a implementação das classes participantes no padrão de desenho. public class Line { private Point p1, p2; private Color c; public Point getP1() { return p1; } public Point getP2() { return p2; } public Color getColor() { return c; } public void setP1(Point p1) { this.p1 = p1; } public void setP2(Point p2) { this.p2 = p2; } public void setColor(Color c) { this.c = c; 145 } } Exemplo 113- classe Line. class Point { private Color c; protected int x = 0; protected int y = 0; public int getX() { return x; } public int getY() { return y; } public void setX(int newX) { x = newX; } public void setY(int newY) { y = newY; } public Color getColor() { return c; } public void setColor(Color c) { this.c = c; } } Exemplo 114- classe Point. public class Screen { public void display(String s) { } public void update() { } } Exemplo 115 - Classe Screen. O padrão de desenho exemplificado apresenta melhor localidade do código, todo o código referente ao padrão de desenho é implementado em aspectos e não nas classes participantes. Qualquer alteração é efectuada num só local, no aspecto. As instâncias do padrão de desenho são localizadas num único local. 146 As classes que participam no padrão de desenho são alheias ao padrão. A forma como o padrão de desenho está implementada, abstractamente. Permite que seja estendido e partilhado por várias instâncias de padrões de desenho, que o implementam em concreto, aspecto colorObserver, aspecto CoordinatorObserver e aspecto ScreenObserver.