PADR˜OES DE PROJETO EM OOERLANG
Transcrição
PADR˜OES DE PROJETO EM OOERLANG
UNIVERSIDADE DO ESTADO DO AMAZONAS - UEA ESCOLA SUPERIOR DE TECNOLOGIA ENGENHARIA DE COMPUTAÇÃO WILLIAM BREMGARTNER BELLEZA PADRÕES DE PROJETO EM OOERLANG Manaus 2013 WILLIAM BREMGARTNER BELLEZA PADRÕES DE PROJETO EM OOERLANG Trabalho de Conclusão de Curso apresentado à banca avaliadora do Curso de Engenharia de Computação, da Escola Superior de Tecnologia, da Universidade do Estado do Amazonas, como pré-requisito para obtenção do tı́tulo de Engenheiro de Computação. Orientador: Prof. Dr. Jucimar Maia da Silva Júnior Manaus 2013 ii Universidade do Estado do Amazonas - UEA Escola Superior de Tecnologia - EST Reitor: Cleinaldo de Almeida Costa Vice-Reitor: Raimundo de Jesus Teixeira Barradas Diretor da Escola Superior de Tecnologia: Cleto Cavalcante de Souza Leal Coordenador do Curso de Engenharia de Computação: Raimundo Corrêa de Oliveira Coordenador da Disciplina Projeto Final: Tiago Eugenio de Melo Banca Avaliadora composta por: Data da Defesa: 18/11/2013. Prof. Dr. Jucimar Maia da Silva Júnior (Orientador) Prof. Dr. Raimundo Corrêa de Oliveira Prof. Dr. Ricardo da Silva Barboza CIP - Catalogação na Publicação B438p BELLEZA, William B. Padrões de Projeto em ooErlang / William Bremgartner Belleza; [orientado por] Prof. Dr. Jucimar Maia da Silva Júnior - Manaus: UEA, 2013. 241 p.: il.; 30cm Inclui Bibliografia Trabalho de Conclusão de Curso (Graduação em Engenharia de Computação). Universidade do Estado do Amazonas, 2013. CDU: 004.43 iii WILLIAM BREMGARTNER BELLEZA PADRÕES DE PROJETO EM OOERLANG Trabalho de Conclusão de Curso apresentado à banca avaliadora do Curso de Engenharia de Computação, da Escola Superior de Tecnologia, da Universidade do Estado do Amazonas, como pré-requisito para obtenção do tı́tulo de Engenheiro de Computação. Aprovado em: 18/11/2013 BANCA EXAMINADORA Prof. Jucimar Maia da Silva Júnior, Doutor UNIVERSIDADE DO ESTADO DO AMAZONAS Prof. Raimundo Corrêa de Oliveira, Doutor UNIVERSIDADE DO ESTADO DO AMAZONAS Prof. Ricardo da Silva Barboza, Doutor UNIVERSIDADE DO ESTADO DO AMAZONAS iv Agradecimentos Agradeço primeiramente a Deus por ter me dado forças ao longo de toda essa caminhada, não apenas na realização deste trabalho como também por todo o perı́odo em que estive na faculdade. Agradeço aos meus pais Wilmar e Maria, por todo o apoio assim como ao meu irmão Brian que muito me incentivou e auxiliou nessa jornada. À minha companheira Miriane pela ajuda e compreensão sempre presentes e que foram o diferencial para mim. Aos amigos com quem tive o privilégio de estudar e, principalmente, ao meu orientador, o prof. Dr. Jucimar Jr., pelos seus ensinamentos e orientações que muito me ajudaram e ajudarão daqui em diante. Muito obrigado a todos! v Resumo Padrões de Projetos são métodos de modelagem para vários tipos de problemas com foco na programação orientada a objetos. Estes padrões foram descobertos e documentados para serem corretamente utilizados quando for preciso, sendo aplicáveis a qualquer linguagem orientada a objetos, baseados nos princı́pios de design para programação orientada a objetos. ooErlang é uma extensão da linguagem Erlang, com suporte à modelagem orientada a objetos. Com a utilização da extensão ooErlang, pode-se modelar problemas utilizando-se dos vários tipos de Padrões de Projetos existentes. Palavras-Chave: Padrões, Projetos, Princı́pios, Objeto, Erlang, ooErlang vi Abstract Design Patterns are modeling methods to several kinds of problems focused in object oriented programming. These patterns were discovered and documented to be correctly used when they are needed, being applicable to any oriented objects programming language, based on the design principles to object-oriented programming. ooErlang is an extension of Erlang language, with support to object oriented modeling. With use of ooErlang extension, it is possible to model problems using various types of Design Patterns. Keywords: Design, Patterns, Principles, Object, Erlang, ooErlang vii Sumário Lista de Figuras xi Lista de Códigos xiii 1 Introdução 1.1 Conceitos do Paradigma Orientado a Objetos . . . . . . . 1.2 Vantagens e problemas da programação orientada a objetos 1.3 Princı́pios de Design . . . . . . . . . . . . . . . . . . . . . 1.4 Padrões de Projeto . . . . . . . . . . . . . . . . . . . . . . 1.4.1 Padrões de Criação . . . . . . . . . . . . . . . . . . 1.4.2 Padrões Estruturais . . . . . . . . . . . . . . . . . . 1.4.3 Padrões Comportamentais . . . . . . . . . . . . . . 1.5 A Linguagem Funcional Erlang . . . . . . . . . . . . . . . 1.6 A linguagem de programação ooErlang . . . . . . . . . . . 1.7 Trabalhos Relacionados . . . . . . . . . . . . . . . . . . . . 1.8 Objetivo Geral . . . . . . . . . . . . . . . . . . . . . . . . 1.9 Objetivos Especı́ficos . . . . . . . . . . . . . . . . . . . . . 1.10 Justificativa . . . . . . . . . . . . . . . . . . . . . . . . . . 1.11 Metodologia . . . . . . . . . . . . . . . . . . . . . . . . . . 1.12 Organização da Monografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Sintaxe do ooErlang 2.1 Conceitos Iniciais . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Estrutura de um fonte em ooErlang . . . . . . . . . . . . . . . . 2.3 Classes com métodos principais e interfaces em ooErlang . . . . 2.4 Utilização de herança e implementação de interface em ooErlang 2.5 Observações finais da sintaxe do ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 6 6 9 10 10 11 11 11 12 13 13 13 14 15 . . . . . 16 16 17 19 21 23 viii 3 Padrões de Projeto de Criação 3.1 Conceitos Iniciais . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Padrão Abstract Factory . . . . . . . . . . . . . . . . . . . . 3.2.1 Exemplo utilizado para este padrão . . . . . . . . . . 3.2.2 Comparação das implementações em Java e ooErlang 3.3 Padrão Builder . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1 Exemplo utilizado para este padrão . . . . . . . . . . 3.3.2 Comparação das implementações em Java e ooErlang 3.4 Padrão Factory Method . . . . . . . . . . . . . . . . . . . . . 3.4.1 Exemplo utilizado para este padrão . . . . . . . . . . 3.4.2 Comparação das implementações em Java e ooErlang 3.5 Padrão Prototype . . . . . . . . . . . . . . . . . . . . . . . . 3.5.1 Exemplo utilizado para este padrão . . . . . . . . . . 3.5.2 Comparação das implementações em Java e ooErlang 3.6 Padrão Singleton . . . . . . . . . . . . . . . . . . . . . . . . 3.6.1 Exemplo utilizado para este padrão . . . . . . . . . . 3.6.2 Comparação das implementações em Java e ooErlang 3.7 Conclusões dos Padrões de Projeto de Criação em ooErlang . 4 Padrões de Projeto Estruturais 4.1 Conceitos Iniciais . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Padrão Adapter . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Exemplo utilizado para este padrão . . . . . . . . . . 4.2.2 Comparação das implementações em Java e ooErlang 4.3 Padrão Bridge . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Exemplo utilizado para este padrão . . . . . . . . . . 4.3.2 Comparação das implementações em Java e ooErlang 4.4 Padrão Composite . . . . . . . . . . . . . . . . . . . . . . . . 4.4.1 Exemplo utilizado para este padrão . . . . . . . . . . 4.4.2 Comparação das implementações em Java e ooErlang 4.5 Padrão Decorator . . . . . . . . . . . . . . . . . . . . . . . . 4.5.1 Exemplo utilizado para este padrão . . . . . . . . . . 4.5.2 Comparação das implementações em Java e ooErlang 4.6 Padrão Facade . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6.1 Exemplo utilizado para este padrão . . . . . . . . . . 4.6.2 Comparação das implementações em Java e ooErlang 4.7 Padrão Flyweight . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 25 26 26 27 31 33 34 39 40 42 47 48 49 53 54 55 59 . . . . . . . . . . . . . . . . . 61 61 62 62 64 68 69 70 74 75 77 82 83 84 89 90 91 97 ix 4.8 4.9 4.7.1 Exemplo utilizado para este padrão . . . . . . . . . . 4.7.2 Comparação das implementações em Java e ooErlang Padrão Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . 4.8.1 Exemplo utilizado para este padrão . . . . . . . . . . 4.8.2 Comparação das implementações em Java e ooErlang Conclusões dos Padrões de Projeto Estruturais em ooErlang 5 Padrões de Projeto Comportamentais 5.1 Conceitos Iniciais . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Padrão Chain of Responsibility . . . . . . . . . . . . . . . . . 5.2.1 Exemplo utilizado para este padrão . . . . . . . . . . 5.2.2 Comparação das implementações em Java e ooErlang 5.3 Padrão Command . . . . . . . . . . . . . . . . . . . . . . . . 5.3.1 Exemplo utilizado para este padrão . . . . . . . . . . 5.3.2 Comparação das implementações em Java e ooErlang 5.4 Padrão Interpreter . . . . . . . . . . . . . . . . . . . . . . . 5.4.1 Exemplo utilizado para este padrão . . . . . . . . . . 5.4.2 Comparação das implementações em Java e ooErlang 5.5 Padrão Iterator . . . . . . . . . . . . . . . . . . . . . . . . . 5.5.1 Exemplo utilizado para este padrão . . . . . . . . . . 5.5.2 Comparação das implementações em Java e ooErlang 5.6 Padrão Mediator . . . . . . . . . . . . . . . . . . . . . . . . 5.6.1 Exemplo utilizado para este padrão . . . . . . . . . . 5.6.2 Comparação das implementações em Java e ooErlang 5.7 Padrão Memento . . . . . . . . . . . . . . . . . . . . . . . . 5.7.1 Exemplo utilizado para este padrão . . . . . . . . . . 5.7.2 Comparação das implementações em Java e ooErlang 5.8 Padrão Observer . . . . . . . . . . . . . . . . . . . . . . . . 5.8.1 Exemplo utilizado para este padrão . . . . . . . . . . 5.8.2 Comparação das implementações em Java e ooErlang 5.9 Padrão State . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.9.1 Exemplo utilizado para este padrão . . . . . . . . . . 5.9.2 Comparação das implementações em Java e ooErlang 5.10 Padrão Strategy . . . . . . . . . . . . . . . . . . . . . . . . . 5.10.1 Exemplo utilizado para este padrão . . . . . . . . . . 5.10.2 Comparação das implementações em Java e ooErlang 5.11 Padrão Template Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 100 106 107 108 114 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 115 116 117 118 123 124 125 134 135 135 143 143 144 154 155 156 163 163 164 170 171 171 179 180 181 188 189 189 196 x 5.11.1 Exemplo utilizado para este padrão . . . . . . . . . . . . . 5.11.2 Comparação das implementações em Java e ooErlang . . . 5.12 Padrão Visitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.12.1 Exemplo utilizado para este padrão . . . . . . . . . . . . . 5.12.2 Comparação das implementações em Java e ooErlang . . . 5.13 Conclusões dos Padrões de Projeto Comportamentais em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 199 206 207 208 215 6 Conclusão 217 6.1 Trabalhos Futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 Referências Bibliográficas 220 xi Lista de Figuras 1.1 1.2 1.3 1.4 1.5 1.6 Exemplo Exemplo Exemplo Exemplo Exemplo Exemplo de de de de de de uma classe chamada “Pessoa”. . . . . . . . . . . . . . . . . . . uma associação entre as classes “Produto” e “Fornecedor”. . . . uma agregação da classe “Produto” na classe “Cesta”. . . . . . uma composição da classe “Fornecedor” na classe “Contato”. . herança entre superclasses e subclasses na orientação à objetos. uma interface “IAluno” e sua implementação na classe “Aluno”. 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 3.15 Diagrama de classes do exemplo para o padrão Abstract Factory. . . Execução do exemplo para o padrão Abstract Factory em Java. . . . Execução do exemplo para o padrão Abstract Factory em ooErlang. Diagrama de classes do exemplo para o padrão Builder. . . . . . . . Execução do exemplo para o padrão Builder em Java. . . . . . . . . Execução do exemplo para o padrão Builder em ooErlang. . . . . . Diagrama de classes do exemplo para o padrão Factory Method. . . Execução do exemplo para o padrão Factory Method em Java. . . . Execução do exemplo para o padrão Factory Method em ooErlang. . Diagrama de classes do exemplo para o padrão Prototype. . . . . . . Execução do exemplo para o padrão Prototype em Java. . . . . . . . Execução do exemplo para o padrão Prototype em ooErlang. . . . . Diagrama de classes do exemplo para o padrão Singleton. . . . . . . Execução do exemplo para o padrão Singleton em Java. . . . . . . . Execução do exemplo para o padrão Singleton em ooErlang. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 33 34 35 40 41 42 47 48 49 52 53 54 58 59 4.1 4.2 4.3 4.4 4.5 Diagrama de classes do exemplo para o padrão Adapter. . Execução do exemplo para o padrão Adapter em Java. . . Execução do exemplo para o padrão Adapter em ooErlang. Diagrama de classes do exemplo para o padrão Bridge. . . Execução do exemplo para o padrão Bridge em Java. . . . . . . . . . . . . . . . . . . . . . . . 63 67 68 70 74 . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 3 4 4 6 xii 4.6 4.7 4.8 4.9 4.10 4.11 4.12 4.13 4.14 4.15 4.16 4.17 4.18 4.19 4.20 4.21 Execução do exemplo para o padrão Bridge em ooErlang. . . Diagrama de classes do exemplo para o padrão Composite. . Execução do exemplo para o padrão Composite em Java. . . Execução do exemplo para o padrão Composite em ooErlang. Diagrama de classes do exemplo para o padrão Decorator. . Execução do exemplo para o padrão Decorator em Java. . . Execução do exemplo para o padrão Decorator em ooErlang. Diagrama de classes do exemplo para o padrão Facade. . . . Execução do exemplo para o padrão Facade em Java. . . . . Execução do exemplo para o padrão Facade em ooErlang. . . Diagrama de classes do exemplo para o padrão Flyweight. . . Execução do exemplo para o padrão Flyweight em Java. . . . Execução do exemplo para o padrão Flyweight em ooErlang. Diagrama de classes do exemplo para o padrão Proxy. . . . . Execução do exemplo para o padrão Proxy em Java. . . . . . Execução do exemplo para o padrão Proxy em ooErlang. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 76 81 82 84 89 90 92 98 99 100 106 107 108 112 113 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14 5.15 5.16 5.17 5.18 5.19 5.20 Diagrama de classes do exemplo para o padrão Chain of Responsibility. . . Execução do exemplo para o padrão Chain of Responsibility em Java. . . . Execução do exemplo para o padrão Chain of Responsibility em ooErlang. . Diagrama de classes do exemplo para o padrão Command. . . . . . . . . . Execução do exemplo para o padrão Command em Java. . . . . . . . . . . Execução do exemplo para o padrão Command em ooErlang. . . . . . . . . Diagrama de classes do exemplo para o padrão Interpreter. . . . . . . . . . Execução do exemplo para o padrão Interpreter em Java. . . . . . . . . . . Execução do exemplo para o padrão Interpreter em ooErlang. . . . . . . . Diagrama de classes do exemplo para o padrão Iterator. . . . . . . . . . . . Execução do exemplo para o padrão Iterator em Java. . . . . . . . . . . . . Execução do exemplo para o padrão Iterator em ooErlang. . . . . . . . . . Diagrama de classes do exemplo para o padrão Mediator. . . . . . . . . . . Execução do exemplo para o padrão Mediator em Java. . . . . . . . . . . . Execução do exemplo para o padrão Mediator em ooErlang. . . . . . . . . Diagrama de classes do exemplo para o padrão Memento. . . . . . . . . . . Execução do exemplo para o padrão Memento em Java. . . . . . . . . . . . Execução do exemplo para o padrão Memento em ooErlang. . . . . . . . . Diagrama de classes do exemplo para o padrão Observer. . . . . . . . . . . Execução do exemplo para o padrão Observer em Java. . . . . . . . . . . . 117 122 123 125 133 134 136 141 142 145 153 154 156 161 162 164 169 170 172 178 xiii 5.21 5.22 5.23 5.24 5.25 5.26 5.27 5.28 5.29 5.30 5.31 5.32 5.33 Execução do exemplo para o padrão Observer em ooErlang. . . . . Diagrama de classes do exemplo para o padrão State. . . . . . . . . Execução do exemplo para o padrão State em Java. . . . . . . . . . Execução do exemplo para o padrão State em ooErlang. . . . . . . . Diagrama de classes do exemplo para o padrão Strategy. . . . . . . Execução do exemplo para o padrão Strategy em Java. . . . . . . . Execução do exemplo para o padrão Strategy em ooErlang. . . . . . Diagrama de classes do exemplo para o padrão TemplateMethod. . . Execução do exemplo para o padrão Template Method em Java. . . Execução do exemplo para o padrão Template Method em ooErlang. Diagrama de classes do exemplo para o padrão Visitor. . . . . . . . Execução do exemplo para o padrão Visitor em Java. . . . . . . . . Execução do exemplo para o padrão Visitor em ooErlang. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 181 187 188 190 196 197 198 205 206 208 214 215 xiv Lista de Códigos 2.2.1 Exemplo de uma classe chamada Pessoa em ooErlang . . . . . . . . . . . . 18 2.3.1 Exemplo de uma classe chamada GerenciadorDePessoas em ooErlang . . . 20 2.3.2 Exemplo de uma interface chamada Figura em ooErlang . . . . . . . . . . 21 2.4.1 Exemplo de uma classe chamada Soma em ooErlang . . . . . . . . . . . . . 22 2.4.2 Exemplo de uma classe chamada Circulo em ooErlang . . . . . . . . . . . . 23 2.5.1 Exemplo de uma classe chamada Main em ooErlang . . . . . . . . . . . . . 24 3.2.1 Interface Cheese em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.2.2 Interface Cheese em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.2.3 Interface PizzaIngredientFactory em Java . . . . . . . . . . . . . . . . . . . 30 3.2.4 Interface PizzaIngredientFactory em ooErlang . . . . . . . . . . . . . . . . 30 3.2.5 Classe principal PizzaTestDrive em Java . . . . . . . . . . . . . . . . . . . 31 3.2.6 Classe principal PizzaTestDrive em ooErlang . . . . . . . . . . . . . . . . . 32 3.3.1 Classe Pizza em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.3.2 Classe Pizza em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.3.3 Classe Waiter em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.3.4 Classe Waiter em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . 37 3.3.5 Classe principal BuilderExample em Java . . . . . . . . . . . . . . . . . . . 38 3.3.6 Classe principal BuilderExample em ooErlang . . . . . . . . . . . . . . . . 39 3.4.1 Classe PizzaStore em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.4.2 Classe PizzaStore em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.4.3 Classe Pizza em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.4.4 Classe Pizza em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 3.4.5 Classe NYPizzaStore em Java . . . . . . . . . . . . . . . . . . . . . . . . . 46 xv 3.4.6 Classe NYPizzaStore em ooErlang . . . . . . . . . . . . . . . . . . . . . . . 46 3.5.1 Classe Person em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.5.2 Classe Person em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.5.3 Interface Prototype em Java . . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.5.4 Interface Prototype em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . 51 3.6.1 Classe ChocolateBoiler em Java . . . . . . . . . . . . . . . . . . . . . . . . 56 3.6.2 Classe ChocolateBoiler em ooErlang . . . . . . . . . . . . . . . . . . . . . . 57 3.6.3 Classe principal ChocolateController em Java . . . . . . . . . . . . . . . . 57 3.6.4 Classe principal ChocolateController em ooErlang . . . . . . . . . . . . . . 58 4.2.1 Interface Duck em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.2.2 Interface Duck em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.2.3 Classe TurkeyAdapter em Java . . . . . . . . . . . . . . . . . . . . . . . . 65 4.2.4 Classe TurkeyAdapter em ooErlang . . . . . . . . . . . . . . . . . . . . . . 65 4.2.5 Classe principal TurkeyTestDrive em Java . . . . . . . . . . . . . . . . . . 66 4.2.6 Classe principal TurkeyTestDrive em ooErlang . . . . . . . . . . . . . . . . 66 4.3.1 Classe Bike em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.3.2 Classe Bike em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.3.3 Classe Assemble em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 4.3.4 Classe Assemble em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 72 4.3.5 Classe principal BridgePattern em Java . . . . . . . . . . . . . . . . . . . . 72 4.3.6 Classe principal BridgePattern em ooErlang . . . . . . . . . . . . . . . . . 73 4.4.1 Classe Composite em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 4.4.2 Classe Composite em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 78 4.4.3 Classe Row em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 4.4.4 Classe Row em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 4.4.5 Classe principal CompositeDemo em Java . . . . . . . . . . . . . . . . . . 80 4.4.6 Classe principal CompositeDemo em ooErlang . . . . . . . . . . . . . . . . 80 4.5.1 Classe Espresso em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 4.5.2 Classe Expresso em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 85 4.5.3 Classe Soy em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 4.5.4 Classe Soy em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 xvi 4.5.5 Classe principal StarbuzzCoffee em Java . . . . . . . . . . . . . . . . . . . 87 4.5.6 Classe principal StarbuzzCoffee em ooErlang . . . . . . . . . . . . . . . . . 88 4.6.1 Classe DvdPlayer em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 4.6.2 Classe DvdPlayer em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 94 4.6.3 Classe HomeTheaterFacade em Java . . . . . . . . . . . . . . . . . . . . . 95 4.6.4 Classe HomeTheaterFacade em ooErlang . . . . . . . . . . . . . . . . . . . 96 4.6.5 Classe principal HomeTheaterTestDrive em Java . . . . . . . . . . . . . . . 97 4.6.6 Classe principal HomeTheaterTestDrive em ooErlang . . . . . . . . . . . . 97 4.7.1 Classe TeaFlavor em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 4.7.2 Classe TeaFlavor em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 101 4.7.3 Classe TeaFlavorFactory em Java . . . . . . . . . . . . . . . . . . . . . . . 102 4.7.4 Classe TeaFlavorFactory em ooErlang . . . . . . . . . . . . . . . . . . . . . 103 4.7.5 Classe TeaRestroom em Java . . . . . . . . . . . . . . . . . . . . . . . . . . 104 4.7.6 Classe TeaRestroom em ooErlang . . . . . . . . . . . . . . . . . . . . . . . 105 4.8.1 Classe RealImage em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 4.8.2 Classe RealImage em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 109 4.8.3 Classe ProxyImage em Java . . . . . . . . . . . . . . . . . . . . . . . . . . 110 4.8.4 Classe ProxyImage em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . 110 4.8.5 Classe principal ProxyExample em Java . . . . . . . . . . . . . . . . . . . . 111 4.8.6 Classe principal ProxyExample em ooErlang . . . . . . . . . . . . . . . . . 111 5.2.1 Interface Chain em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 5.2.2 Interface Chain em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . 118 5.2.3 Classe PositiveProcessor em Java . . . . . . . . . . . . . . . . . . . . . . . 119 5.2.4 Classe PositiveProcessor em ooErlang . . . . . . . . . . . . . . . . . . . . . 120 5.2.5 Classe principal TestChain em Java . . . . . . . . . . . . . . . . . . . . . . 120 5.2.6 Classe principal TestChain em ooErlang . . . . . . . . . . . . . . . . . . . 121 5.3.1 Classe LightOnCommand em Java . . . . . . . . . . . . . . . . . . . . . . 126 5.3.2 Classe LightOnCommand em ooErlang . . . . . . . . . . . . . . . . . . . . 127 5.3.3 Classe Light em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 5.3.4 Classe Light em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 5.3.5 Classe RemoteControlWithUndo em Java . . . . . . . . . . . . . . . . . . . 130 xvii 5.3.6 Classe RemoteControlWithUndo em ooErlang . . . . . . . . . . . . . . . . 130 5.3.7 Classe principal RemoteLoader em Java . . . . . . . . . . . . . . . . . . . . 131 5.3.8 Classe principal RemoteLoader em ooErlang . . . . . . . . . . . . . . . . . 132 5.4.1 Classe Plus em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 5.4.2 Classe Plus em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 5.4.3 Classe Evaluator em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 5.4.4 Classe Evaluator em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 139 5.4.5 Classe principal InterpreterExample em Java . . . . . . . . . . . . . . . . . 140 5.4.6 Classe principal InterpreterExample em ooErlang . . . . . . . . . . . . . . 140 5.5.1 Classe DinerMenu em Java . . . . . . . . . . . . . . . . . . . . . . . . . . 146 5.5.2 Classe DinerMenu em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . 147 5.5.3 Classe PancakeHouseMenu em Java . . . . . . . . . . . . . . . . . . . . . . 148 5.5.4 Classe PancakeHouseMenu em ooErlang . . . . . . . . . . . . . . . . . . . 148 5.5.5 Classe DinerMenuIterator em Java . . . . . . . . . . . . . . . . . . . . . . 149 5.5.6 Classe DinerMenuIterator em ooErlang . . . . . . . . . . . . . . . . . . . . 150 5.5.7 Classe Waitress em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 5.5.8 Classe Waitress em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 152 5.5.9 Classe principal MenuTestDrive em Java . . . . . . . . . . . . . . . . . . . 152 5.5.10 Classe principal MenuTestDrive em ooErlang . . . . . . . . . . . . . . . . 153 5.6.1 Classe Flight em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 5.6.2 Classe Flight em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 5.6.3 Classe ATCMediator em Java . . . . . . . . . . . . . . . . . . . . . . . . . 158 5.6.4 Classe ATCMediator em ooErlang . . . . . . . . . . . . . . . . . . . . . . . 159 5.6.5 Classe principal MediatorExample em Java . . . . . . . . . . . . . . . . . . 160 5.6.6 Classe principal MediatorExample em ooErlang . . . . . . . . . . . . . . . 160 5.7.1 Classe Memento em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 5.7.2 Classe Memento em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . 165 5.7.3 Classe Originator em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 5.7.4 Classe Originator em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . 166 5.7.5 Classe principal MementoExample em Java . . . . . . . . . . . . . . . . . . 167 5.7.6 Classe principal MementoExample em ooErlang . . . . . . . . . . . . . . . 168 xviii 5.8.1 Classe CurrentConditionsDisplay em Java . . . . . . . . . . . . . . . . . . 173 5.8.2 Classe CurrentConditionsDisplay em ooErlang . . . . . . . . . . . . . . . . 174 5.8.3 Classe WeatherData em Java . . . . . . . . . . . . . . . . . . . . . . . . . 175 5.8.4 Classe WeatherData em ooErlang . . . . . . . . . . . . . . . . . . . . . . . 176 5.8.5 Classe principal WeatherStation em Java . . . . . . . . . . . . . . . . . . . 177 5.8.6 Classe principal WeatherStation em ooErlang . . . . . . . . . . . . . . . . 177 5.9.1 Classe NoQuarterState em Java . . . . . . . . . . . . . . . . . . . . . . . . 182 5.9.2 Classe NoQuarterState em ooErlang . . . . . . . . . . . . . . . . . . . . . . 183 5.9.3 Classe GumballMachine em Java . . . . . . . . . . . . . . . . . . . . . . . 184 5.9.4 Classe GumballMachine em ooErlang . . . . . . . . . . . . . . . . . . . . . 184 5.9.5 Classe principal GumballMachineTestDrive em Java . . . . . . . . . . . . . 185 5.9.6 Classe principal GumballMachineTestDrive em ooErlang . . . . . . . . . . 186 5.10.1 Classe Duck em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 5.10.2 Classe Duck em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 5.10.3 Classe MallardDuck em Java . . . . . . . . . . . . . . . . . . . . . . . . . 192 5.10.4 Classe MallardDuck em ooErlang . . . . . . . . . . . . . . . . . . . . . . . 192 5.10.5 Classe FlyWithWings em Java . . . . . . . . . . . . . . . . . . . . . . . . 192 5.10.6 Classe FlyWithWings em ooErlang . . . . . . . . . . . . . . . . . . . . . . 193 5.10.7 Classe principal MiniDuckSimulator em Java . . . . . . . . . . . . . . . . 194 5.10.8 Classe principal MiniDuckSimulator em ooErlang . . . . . . . . . . . . . . 195 5.11.1 Classe CaffeineBeverage em Java . . . . . . . . . . . . . . . . . . . . . . . 199 5.11.2 Classe CaffeineBeverage em ooErlang . . . . . . . . . . . . . . . . . . . . 200 5.11.3 Classe Coffee em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 5.11.4 Classe Coffee em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . 201 5.11.5 Classe CoffeeWithHook em Java . . . . . . . . . . . . . . . . . . . . . . . 202 5.11.6 Classe CoffeeWithHook em ooErlang . . . . . . . . . . . . . . . . . . . . . 203 5.11.7 Classe principal BeverageTestDrive em Java . . . . . . . . . . . . . . . . . 204 5.11.8 Classe principal BeverageTestDrive em ooErlang . . . . . . . . . . . . . . 204 5.12.1 Classe Car em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 5.12.2 Classe Car em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 5.12.3 Classe Wheel em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 xix 5.12.4 Classe Wheel em ooErlang . . . . . . . . . . . . . . . . . . . . . . . . . . 211 5.12.5 Classe CarElementDoVisitor em Java . . . . . . . . . . . . . . . . . . . . 211 5.12.6 Classe CarElementDoVisitor em ooErlang . . . . . . . . . . . . . . . . . . 212 5.12.7 Classe principal VisitorExample em Java . . . . . . . . . . . . . . . . . . 213 5.12.8 Classe principal VisitorExample em ooErlang . . . . . . . . . . . . . . . . 213 Capı́tulo 1 Introdução O paradigma de programação orientado a objetos busca uma forma de programação que tenta imitar o mundo real. Neste paradigma são trabalhados vários objetos, assim como na realidade. E cada objeto possui atributos e métodos, podendo conversar, trocar mensagens e interagir entre si. Cada objeto pode ser reutilizado para outras finalidades. A orientação a objetos é um modo natural de pensar sobre o mundo e de escrever programas de computador [Deitel and Deitel, 2010]. Para melhor compreensão, são explicados na seção sobre conceitos do paradigma orientado a objetos as principais definições que caracterizam e são utilizadas de forma geral na orientação à objetos. 1.1 Conceitos do Paradigma Orientado a Objetos Assim como os paradigmas de programação funcional e estruturado, o paradigma orientado a objetos possui algumas caracterı́sticas e conceitos que necessitam ser compreendidos para melhor entendimento do mesmo. Os conceitos explicados nesta seção são: Objeto, Classe, Associação, Agregação, Composição, Herança, Polimorfismo (e suas derivações), Encapsulamento e Interfaces. • Objeto: É uma entidade que possui atributos, comportamentos, e se relaciona com outros objetos por meio de mensagens. Um objeto pode ser algo concreto ou abstrato. Conceitos do Paradigma Orientado a Objetos 2 Objetos propiciam maior compreensão do mundo real e possuem responsabilidade sobre si [Dall’Oglio, 2009]. Dessa forma podemos dizer que objetos são um dos principais focos deste paradigma. • Classe: É uma estrutura estática utilizada para descrever (moldar) objetos, sendo um modelo para criação de objetos [Dall’Oglio, 2009]. Um exemplo de classe pode ser visto na figura 1.1. Existem dois tipos de classes, entidades do negócio da aplicação, sendo as classes diretamente relacionadas com a aplicação e entidades de interface, que estão relacionadas com a parte gráfica de uma aplicação. Um objeto é uma instancia de uma classe [Dall’Oglio, 2009]. Figura 1.1: Exemplo de uma classe chamada “Pessoa”. • Associação: É a relação mais comum entre dois objetos, na qual um objeto possui uma referência à outro objeto, podendo visualizar seus atributos ou mesmo acionar uma de suas funcionalidades [Dall’Oglio, 2009]. Um forma simples para se obter uma associação, seria a de utilizar um objeto como atributo de outro objeto, desta forma um objeto está relacionado ao outro. Um exemplo de associação pode ser observado na figura 1.2. • Agregação: É o tipo de relação entre objetos conhecida como todo/parte, na qual um objeto referencia objeto(s) externo(s) dentro de si [Dall’Oglio, 2009]. Uma forma simples de implementar a agregação é utilizando uma lista como atributo de um objeto, possuindo diversas instâncias deste objeto nesta lista. O objeto-pai poderá Conceitos do Paradigma Orientado a Objetos 3 Figura 1.2: Exemplo de uma associação entre as classes “Produto” e “Fornecedor”. agregar uma ou muitas instâncias de outro objeto e poderá utilizar funcionalidades do objeto agregado [Dall’Oglio, 2009]. Dessa forma, um objeto contém instâncias de outros objetos. Pode-se observar uma forma de utilização da agregação ilustrada na figura 1.3. A principal diferença em relação à associação é que na agregação existe mais em um objeto sendo utilizado como atributo de outra classe (arranjo de objetos, por exemplo). Figura 1.3: Exemplo de uma agregação da classe “Produto” na classe “Cesta”. • Composição: Assim como a agregação, a composição também é uma relação todo/parte. A diferença está no fato de que o “todo” e as “partes” são dependentes entre si, ou seja, o objeto-pai possui realmente suas partes, sendo responsável por sua criação e destruição. Quando o objeto “todo” é destruı́do, suas “partes” também são [Dall’Oglio, 2009]. Ou seja, um objeto é composto de outros objetos. A figura 1.4 mostra um exemplo de utilização da composição. • Herança: É um tipo de relacionamento que permite especializar uma classe, criar versões refinadas dela [Dall’Oglio, 2009]. A herança é um bom exemplo de reutilização de códigos na orientação a objetos, quando torna possı́vel criar novas classes que herdam atributos e métodos da classe originária. A herança é uma forma de reutilizar componentes de software aperfeiçoandos-os ou adicionando caracterı́sticas Conceitos do Paradigma Orientado a Objetos 4 Figura 1.4: Exemplo de uma composição da classe “Fornecedor” na classe “Contato”. especı́ficas [Dall’Oglio, 2009]. Uma forma de se utilizar herança está mostrada na figura 1.5, na qual as classes “Funcionário” e “Prestador” são herdeiras da classe “Colaborador”, assim como herdam seus atributos para as classes “Empregado”, “Estagiário”, “Empresa” e “Autônomo”. Figura 1.5: Exemplo de herança entre superclasses e subclasses na orientação à objetos. • Polimorfismo: Permite que classes derivadas de uma mesma super classe tenham métodos com a mesma nomenclatura mas comportamentos diferentes, redefinidos em cada uma das classes-filha [Dall’Oglio, 2009]. Seu significado refere-se a “muitas formas”, por permitir métodos semelhantes com execuções diferentes. O polimorfismo pode ser classificado em três formas distintas: Polimorfismo de Sobrecarga, de Sobreposição e de Inclusão. Conceitos do Paradigma Orientado a Objetos 5 – Polimorfismo de Sobrecarga: Este polimorfismo é caracterizado por possuir dois ou mais métodos com o mesmo nome, porém diferentes quantidades e tipos de parâmetros. Para cada método haverá um comportamento diferente e, apesar do nome ser semelhante, o resultado será distinto, dependendo dos parâmetros de entrada do método em questão. – Polimorfismo de Sobreposição: Nesta classe de polimorfismo, um método de uma superclasse é redefinido por uma subclasse que o herda. Neste caso, apesar de o método possuir mesmo nome e mesmos parâmetros, quando utilizado pela subclasse, o comportamento será diferente, devido ao fato do método ter sido sobreposto ao método originalmente herdado da superclasse. – Polimorfismo de Inclusão: Nesta classe de polimorfismo as classes que herdam de uma superclasse são utilizadas para substituir a superclasse na construção de um objeto (passando a classe por parâmetro), por exemplo. Devido a isto, cada subclasse que substituir a superclasse irá fazer com que o objeto possua um comportamento diferente, relacionado com a classe que substituiu a superclasse. • Encapsulamento: É uma caracterı́stica de todas as linguagens de programação que implementam tipos abstratos de dados. Na orientação a objetos, visa proteger aspectos dos objetos que não devem ser modificados ou vistos dos aspectos que são visı́veis aos usuários. Trata-se de uma proteção a um conteúdo do objeto, separando os aspectos externos de um objeto de seus detalhes internos. É uma forma de proteger certos atributos, evitando que os mesmos contenham valores inconsistentes ou sejam manipulados indevidamente [Dall’Oglio, 2009]. • Interface: Sabendo-se que os objetos relacionam-se entre si e que é importante que um objeto saiba quais métodos os objetos com quem ele se relaciona possuem, interfaces são tipos de classes que possuem apenas uma assinatura dos métodos que uma classe deverá implementar. Uma classe que implementar uma interface deverá obrigatoriamente possuir os métodos pré-definidos na interface [Dall’Oglio, 2009]. A figura 1.6 mostra a utilização de uma interface chamada “IAluno”, onde seus métodos são implementados em uma classe chamada “Aluno”. Vantagens e problemas da programação orientada a objetos 6 Figura 1.6: Exemplo de uma interface “IAluno” e sua implementação na classe “Aluno”. 1.2 Vantagens e problemas da programação orientada a objetos Uma das principais vantagens no uso do paradigma de orientação a objetos para modelagem de projetos é a possibilidade da reutilização de códigos. A facilidade de codificação é outro fator positivo neste paradigma. Esta facilidade é consequência de outro fator vantajoso na orientação a objetos, o alto nı́vel de abstração desta linguagem. Para projetos bem modelados, o ciclo de vida de um sistema pode ser bastante longo, pois a dificuldade de manutenção e reutilização do código torna-se pequena. A programação orientada a objetos também possui algumas desvantagens, podendo ser citado entre elas: complexidade no aprendizado para desenvolvedores de linguagens de programação estruturada, maior esforço na modelagem de um sistema orientado a objetos em relação ao estruturado (apesar da codificação possuir um nı́vel de abstração maior em comparação com linguagens de programação estruturadas), funcionalidades limitadas por interfaces ainda não completadas, dependência de funcionalidades já implementadas em superclasses, no caso especı́fico da utilização de herança, por exemplo. 1.3 Princı́pios de Design Ao estruturar um projeto orientado a objetos, geralmente observam-se padrões de comportamento de objetos que podem ser semelhantes ou de alguma forma parecidos. Um Princípios de Design 7 dos objetivos da programação orientada a objetos é o de simplificar as implementações o máximo possı́vel. Neste caso de semelhança entre objetos, existe uma forma de implementação chamada “herança”, em que uma classe superior, chamada “superclasse”, tem seus métodos herdados para outras classes semelhantes a ela. Desta forma, pode existir uma classe mais “geral”, como por exemplo “insetos”, e várias classes mais especı́ficas, por exemplo, “mosca”, “formiga”, “gafanhoto” entre outras. E todos os métodos que fossem comuns a todos os tipos de insetos iriam estar na classe inseto e todas as classes que herdassem esta classe também herdariam os métodos da superclasse. Esta forma de implementação facilita por simplificar o desenvolvimento de uma solução para certo problema ou situação. Entretanto, a herança possui uma caracterı́stica importante a ser observada: acoplamento forte entre classes. Isto significa dizer que sempre que uma superclasse for modificada, esta mudança irá influenciar todas as subclasses que a herdam. E desta forma a reutilização de código torna-se algo mais difı́cil de ser realizada, assim como a manutenção. E é neste aspecto (e em outros) que foram documentados os princı́pios de design na programação orientada a objetos, assim como os padrões de projetos, que são importantes por utilizarem diferentes soluções que podem ser aplicadas a situações conhecidas, tendo como objetivo um código o menos acoplado possı́vel (acoplamento é quando uma classe é dependente de uma ou várias classes), mais fácil de ser reutilizado, mantido e modificado. Os princı́pios de design para programação orientada a objetos foram definidos por diferentes autores, sendo documentados e publicados por Robert Cecil Martin em meados de 2000. No total são 11 (onze) princı́pios básicos para que se obtenha um design de projeto orientado a objetos desacoplado, de fácil manutenção, extensão e reutilização. Vejamos estes princı́pios com mais detalhes: • The Single Responsibility Principle (Princı́pio da responsabilidade única): Uma classe deve possuir apenas uma razão para mudar [Martin and Martin, 2006]. Isto significa dizer que não deve haver mais de uma razão para uma classe mudar. Cada classe deve possuir apenas uma responsabilidade e, se uma classe possuir mais de uma responsabilidade, deve-se decompor esta classe em outras subclasses. • The Open-Closed Principle (Princı́pio aberto-fechado): Entidades de software (classes, módulos, funções etc) devem ser abertos para extensão, porém fechados para Princípios de Design 8 modificação [Martin and Martin, 2006]. Este princı́pio orienta que cada entidade de software não pode ser modificada, apenas estendida em suas funcionalidades, evitando assim erros de projetos mal modelados. • The Liskov Substitution Principle (Princı́pio da substituição de Liskov): Subtipos devem ser substituı́veis por seus tipos bases [Martin and Martin, 2006]. Este princı́pio defende que uma instância de uma subclasse deve poder substituir uma instância de sua superclasse, sem que isso cause problema na execução do programa. • The Dependency Inversion Principle (Princı́pio da inversão de dependência): Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações [Martin and Martin, 2006]. A idéia deste princı́pio é a de que classes devem depender de classes mais abstratas ou de interfaces, por exemplo. Isto evita dependências desnecessárias entre classes, permitindo um código mais desacoplado. • The Interface Segregation Principle (Princı́pio da segregação de interface): Clientes não devem ser forçados a depender de métodos que eles não utilizam. Interfaces pertencem a clientes e não a hierarquias [Martin and Martin, 2006]. De acordo com este princı́pio, devem ser evitadas interfaces que possuem muitos métodos, não usados por clientes, assim como se deve evitar forçar o cliente a utilizar um método de uma interface que o mesmo não queira. • The Release-Reuse Equivalency Principle (Princı́pio da Equivalência de Liberação e Reuso): A granularidade do reuso é a granularidade da liberação [Martin and Martin, 2006]. O fundamento deste princı́pio visa à integração de mudanças na reutilização de códigos. Não é certo reutilizar códigos apenas copiando de uma classe para outra, pois isto poderia gerar futuros problemas de ambiguidade. Deve existir um controle quando ocorrem mudanças em um pacote, para que não afete o código já existente. • The Common Closure Principle (Princı́pio do acoplamento comum): As classes em um pacote devem ser acopladas juntas contra qualquer tipo de mudanças. Uma mudança que afeta um pacote fechado afeta todas as classes daquele pacote e nenhum outro pacote [Martin and Martin, 2006]. Para este princı́pio, todas as classes de um Padrões de Projeto 9 pacote e apenas deste pacote devem ser afetados quando ocorre uma modificação neste pacote. Isto permite a integridade de classes dentro de um pacote. • The Common Reuse Principle (Princı́pio da reutilização comum): As classes em um pacote são reutilizadas juntas. Se você reutilizar uma das classes em um pacote, você irá reutilizar todas elas [Martin and Martin, 2006]. Isto existe pelo fato de cada pacote possuir um conjunto de classes liberadas, ou seja, uma mudança em uma classe significa uma liberação nova de todo o pacote. • The Acyclic Dependencies Principle (Princı́pio das dependências acı́clicas): Não permita ciclos no grafo de dependência de pacotes [Martin and Martin, 2006]. Este princı́pio defende que a estrutura de dependências entre pacotes deve ser um grafo acı́clico, sendo proibida a possibilidade de, ao seguir qualquer linha de dependência, se retorne a um pacote antes visitado. • The Stable Dependencies Principle (Princı́pio das dependências estáveis): Dependência na direção da estabilidade [Martin and Martin, 2006]. Este princı́pio defende que a dependência de pacotes em um design de projetos deve sempre seguir para pacotes mais estáveis, ou seja, pacotes devem sempre depender de outros pacotes mais estáveis que os próprios. • The Stable Abstractions Principle (Princı́pio das abstrações estáveis): Um pacote deve ser tão abstrato quanto é estável [Martin and Martin, 2006]. Isto significa dizer que pacotes que são altamente estáveis devem ser altamente abstratos. Pacotes instáveis devem ser concretos. A abstração de um pacote deve ser diretamente proporcional à sua estabilidade. 1.4 Padrões de Projeto Padrões de Projetos são arquiteturas comprovadas para construir software orientado a objetos flexı́vel e fácil de manter [Deitel and Deitel, 2010]. Foram definidos vinte e três (23) diferentes tipos de padrões de projetos. Estes padrões foram desenvolvidos com base em diferentes problemas, quando uma solução trivial não é aplicável a uma determinada Padrões de Projeto 10 situação, sendo subdivididos em diferentes tipos de padrões de projetos, de acordo com sua aplicação, e devidamente documentados para serem utilizados corretamente. Os padrões de projetos já são a solução dos principais tipos de problemas em orientação à objetos [Freeman et al., 2004]. Eles foram definidos e documentados por quatro (4) desenvolvedores de projetos com orientação à objetos, Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides. Estes autores são conhecidos como a “Gang of Four” (gangue dos quatro), ou simplesmente GoF. A correta utilização destes padrões é bastante recomendável, tendo em vista que ajudam na coesão, consistência, manutenção, reutilização e melhoria de código-fonte. Também auxiliam todo e qualquer desenvolvedor a melhorar sua modelagem de um problema especı́fico. Podem ser utilizados isoladamente ou em conjunto, não tendo entre si uma utilização isolada ou mutuamente exclusiva. Os padrões de projetos foram estudados e definidos de acordo com a sua aplicabilidade e o contexto em que podem ser utilizados. Portanto, cada padrão possui uma implementação caracterı́stica do mesmo. Estes padrões variam na sua granularidade e nı́vel de abstração, sendo necessário organizá-los [Gamma et al., 1994]. Existem algumas formas de classificar todos os padrões de projetos, uma delas é dividindo em três tipos de classificação: padrões de criação, padrões estruturais e padrões comportamentais. 1.4.1 Padrões de Criação São padrões que ajudam a fazer um sistema independente de como seus objetos são criados, construı́dos e representados [Gamma et al., 1994]. Cinco (5) padrões possuem esta classificação: Abstract Factory, Builder, Factory Method, Prototype, Singleton. 1.4.2 Padrões Estruturais Padrões estruturais estão preocupados em como classes e objetos são compostos para formar estruturas maiores [Gamma et al., 1994]. Sete (7) padrões completam esta classificação: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy. A Linguagem Funcional Erlang 1.4.3 11 Padrões Comportamentais Padrões comportamentais estão preocupados com algoritmos e a atribuição de responsabilidades entre objetos, descrevendo não apenas padrões de objetos ou classes, mas também padrões de comunicação entre eles [Gamma et al., 1994]. Ao todo onze (11) padrões estão inclusos nesta classificação: Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor. 1.5 A Linguagem Funcional Erlang Erlang é uma linguagem de programação inicialmente criada para a empresa de telecomunicações Ericsson. Seu nome deriva de “Ericsson Language”. Seu paradigma é funcional, e o principal foco desta linguagem é a capacidade de trabalhar com diversas requisições em paralelo assim como conseguir se recuperar de alguma falha sem deixar todo o sistema inativo. Erlang foi projetado para programação concorrente, em tempo real, com sistemas tolerantes a falhas e distribuı́dos [Armstrong, 1996]. A linguagem de programação Erlang é bastante utilizada por diversos desenvolvedores, por vários motivos: Programas escritos em Erlang executam mais rápido em computadores com múltiplos núcleos de processamento, aplicações em Erlang podem ser tolerantes a falhas e podem ser modificadas em tempo de execução. A linguagem Erlang já foi testada em produtos de larga escala industrial [Armstrong, 2007]. 1.6 A linguagem de programação ooErlang O paradigma de programação funcional é diferente do paradigma orientado a objetos. O primeiro é baseado em funções e seus retornos. O segundo é baseado nos objetos das classes e nas relações entre estes objetos. Fazer com que uma linguagem funcional tenha suporte para orientação a objetos não é tarefa simples, tendo-se em vista que são abordagens bastante distintas, formas diferentes de se modelar um problema especı́fico. Iremos agora descrever o ooErlang. Trata-se de um acréscimo na linguagem de programação Erlang, incluindo a possibilidade de programar com orientação a objetos utilizando- Trabalhos Relacionados 12 se das principais caracterı́sticas da linguagem funcional Erlang, mas com outra abordagem de modelagem. O ooErlang supre o Erlang no que diz respeito à possibilidade de programar com orientação à objetos. Portanto, ooErlang é uma extensão orientada a objetos para a linguagem de programação Erlang [Jr. and Lins, 2012]. A extensão ooErlang foi possı́vel ser desenvolvida com um acréscimo no compilador original da linguagem Erlang, fazendo com que fosse possı́vel trabalhar com orientação a objetos. Isto significa que é possı́vel criar classes, instanciar objetos, programar para interfaces, etc. Tornar o Erlang orientado a objetos pode ser uma estratégia para alcançar um público mais extenso, provavelmente com maior visibilidade e aceitação [Jr. et al., 2012]. Para desenvolvedores da linguagem Erlang que necessitem trabalhar com uma linguagem orientada a objetos, ooErlang é uma alternativa fortemente recomendada, por ter uma sintaxe de fácil assimilação e caracterı́sticas básicas de conceitos de orientação a objetos fáceis de serem implementados e testados [da Silva Júnior, 2013]. Sua sintaxe simples, próxima de outras linguagens de programação largamente utilizadas como Java, tornam fácil sua adoção [Jr. and Lins, 2012]. 1.7 Trabalhos Relacionados • Protótipo de Software para geração de sistemas distribuı́dos utilizando Design Patterns [OSS, 2001]: Este trabalho é de pesquisa e implementação de um protótipo para geração de sistemas distribuı́dos utilizando alguns padrões de projetos; • Um estudo de Design Patterns no desenvolvimento em PHP 5 [Grzesiuk, 2006]: Este trabalho é de estudo e pesquisa acerca da utilização de Design Patterns no desenvolvimento do PHP 5; • Padrões de Projeto: Uma compilação dos mais utilizados em projetos de software [de Souza Pereira, 2008]: Este trabalho está focado no estudo dos padrões de projetos mais utilizados, destacando as caracterı́sticas dos principais. Objetivo Geral 1.8 13 Objetivo Geral Implementar formas de utilização de todos os Padrões de Projetos utilizando-se da extensão da linguagem Erlang chamada ooErlang, testando as implementações realizadas e verificando os resultados obtidos, analisando a execução dos respectivos padrões. 1.9 Objetivos Especı́ficos • Implementar um exemplo prático de utilização dos padrões de projeto para cada um dos cinco (5) Padrões de Criação existentes, em Java e ooErlang; • Implementar um exemplo prático de utilização dos padrões de projeto para cada um dos sete (7) Padrões Estruturais existentes, em Java e ooErlang; • Implementar um exemplo prático de utilização dos padrões de projeto para cada um dos onze (11) Padrões Comportamentais existentes, em Java e ooErlang; • Compilar os fontes em Java e ooErlang resultantes das implementações; • Testar os fontes compilados dos padrões de projetos, verificando se os resultados obtidos em Java e ooErlang são semelhantes. 1.10 Justificativa Para os desenvolvedores que possuem conhecimento na linguagem de programação Erlang, e também necessitem de uma solução para um problema do tipo orientado a objetos, a utilização do ooErlang é uma boa alternativa. Implementar padrões de projetos utilizandose desta extensão do Erlang pode ser uma opção inteligente na construção e manutenção de problemas cuja melhor solução nem sempre é a mais fácil. A extensão ooErlang possui uma sintaxe bastante próxima gramaticalmente de linguagens de programação orientadas a objetos como o Java, o que facilita seu uso e suas aplicações em diversos tipos de problemas ou situações. Desenvolvedores de projetos em linguagem de programação com paradigma orientado a objetos assim como programadores Metodologia 14 da linguagem Erlang podem facilmente se adaptar ao ooErlang, utilizando-o como ferramenta de desenvolvimento de projetos. Mostrar que esta extensão possibilita a implementação de todos os padrões de projetos definidos e documentados, confirma o ooErlang como sendo uma ferramenta bastante útil em diversos tipos de projetos com o foco na orientação à objetos. Os trabalhos relacionados a este que foram citados anteriormente também tratam do tema Design Patterns como foco principal, mas nenhum deles trabalha com todos os vinte e três (23) padrões de projetos existentes. Além disso, nem todos os trabalhos são de desenvolvimento, limitando-se apenas à pesquisa cientı́fica e documentação. 1.11 Metodologia Inicialmente é utilizada a sintaxe da extensão ooErlang para implementar exemplos de utilização de todos os 23 diferentes padrões de projetos documentados, dando ênfase para as caracterı́sticas especı́ficas de cada padrão. Para cada exemplo em ooErlang, existe um exemplo semelhante em Java, para que seja possı́vel comparar o comportamento de ambas as implementações. Cada exemplo procura tratar especificamente do escopo no qual o padrão de projeto visa melhorar. Os exemplos implementados também são documentados e são salvos no repositório Github (sistema de controle de versão e gerenciamento de código-fonte, desenvolvido originalmente para Linux) para que se tenha melhor entendimento da forma como o padrão de projeto é aplicado para resolver determinado problema, assim como para que fique mais claro perceber as pequenas diferenças entre cada tipo de padrão de projeto. Em seguida os padrões implementados são compilados e testados, havendo assim a comprovação da correta implementação e aplicação destes padrões em diferentes problemas. Os resultados obtidos são analisados, com o intuito de verificar se são realmente os resultados que estavam sendo esperados. Todo o processo de desenvolvimento e geração das implementações dos fontes do ooErlang é interativo e incremental. Isto significa dizer que cada implementação é testada e validada antes que se passe à próxima. Para armazenar os códigos implementados de forma segura, é utilizado novamente o repositório Github, salvando-os e mantendo-os atualizados. Organização da Monograa 15 Após o processo de implementação e testes ser concluı́do, é feita a documentação das implementações, dos testes e dos resultados obtidos. Cada padrão implementado e testado irá ser documentado de acordo com suas caracterı́sticas próprias e com sua abordagem no que diz respeito ao problema que o padrão especı́fico procura resolver. 1.12 Organização da Monografia Este trabalho está estruturado nos seguintes capı́tulos: Capı́tulo 2: Sintaxe do ooErlang Neste capı́tulo são apresentadas as principais caracterı́sticas da sintaxe utilizada na codificação de fontes em ooErlang, definindo a estrutura principal de classes e interfaces nesta extensão do Erlang voltada à orientação à objetos. Capı́tulo 3: Padrões de Projeto de Criação Este capı́tulo trata das implementações em ooErlang de exemplos da utilização de todos os padrões de projeto de criação, explicando as caracterı́sticas próprias de cada utilização dos padrões para situações especı́ficas nas quais os mesmos podem ser utilizados. Capı́tulo 4: Padrões de Projeto Estruturais Este capı́tulo mostra os exemplos de utilização dos padrões de projetos estruturais utilizando-se do ooErlang, e de suas caracterı́sticas próprias de sintaxe e regras de implementação. Cada exemplo é detalhado em suas peculiaridades, sendo explicado o motivo para utilização de cada padrão. Capı́tulo 5: Padrões de Projeto Comportamentais Semelhante aos capı́tulos anteriores, este mostra como o ooErlang pode ser utilizado para aplicar os padrões de projetos que se encaixam na classificação dos comportamentais, explicando as caracterı́sticas próprias de cada padrão e as decisões de implementação dos mesmos. Capı́tulo 6: Conclusão e Trabalhos Futuros Neste capı́tulo é apresentada a conclusão obtida após o desenvolvimento de todo o projeto, analisando os resultados provenientes das implementações dos exemplos dos padrões, assim como são sugeridos trabalhos futuros relacionados a este tema. Capı́tulo 2 Sintaxe do ooErlang Neste capı́tulo é tratado a respeito da sintaxe própria da extensão do Erlang para suportar o paradigma orientado à objetos, ooErlang. A extensão ooErlang tem como base fundamental o próprio Erlang. O compilador do ooErlang é o mesmo compilador do Erlang, porém com as modificações necessárias para o suporte da orientação à objetos. A sintaxe de uma forma geral é silimar à utilizada no próprio Erlang. Algumas palavraschaves são introduzidas no compilador do ooErlang para facilitar o uso desta extensão, tornando-a bastante próxima, sintaticamente da linguagem Java, por exemplo. Mesmo assim, os elementos sintáticos do Erlang ainda estão presentes no ooErlang, uma vez que a estrutura principal do compilador original do Erlang não modificou-se consideravelmente. 2.1 Conceitos Iniciais A extensão da linguagem de programação ooErlang possui uma sintaxe própria, com elementos presentes do Erlang e também bastante semelhante à sintaxe da linguagem Java. Com a utilização desta extensão é possivel criar classes, classes abstratas e interfaces, cada uma com suas caracterı́sticas próprias. O objetivo deste capı́tulo é o de explicar o ooErlang de acordo com sua sintaxe, de uma forma geral, mostrando exemplos ilustrativos de fontes em ooErlang. Na criação de uma classe ou interface em ooErlang, deve-se observar sempre que exis- Estrutura de um fonte em ooErlang 17 tem três partes bem definidas ao longo do código-fonte, sendo elas: Declarações Iniciais, Atributos e Métodos. Obrigatoriamente estas partes aparecem nesta devida ordem, pois é desta forma que o compilador do ooErlang funciona. A alteração da ordem desta estrutura ocasiona erro de compilação e falha no processo de geração de executáveis. A seguir são melhor explicadas cada uma das partes. • Declarações Iniciais: Encontra-se no cabeçalho do fonte, sendo utilizado para definir se estamos tratando de uma classe ou interface. Também define herança entre classes, implementação de interfaces, declaração de métodos públicos e declaração do método construtor, no caso da classe possuir um. • Atributos: A segunda parte do fonte, responsável por declarar todos os atributos pertencentes à referente classe que está sendo implementada. • Métodos: Esta é a parte final do fonte em ooErlang, em que são definidos os métodos utilizados pelas instâncias pertencentes à referente classe. Estes métodos podem ser públicos ou privados, e isso é definido nas declarações iniciais. 2.2 Estrutura de um fonte em ooErlang Para melhor visualizar as três partes principais dos fontes em ooErlang, observemos o fonte chamado pessoa.cerl mostrado em 2.2.1 Inicialmente verifica-se que todos os fontes em ooErlang possuem seu nome iniciando com letra minúscula. A extensão utilizada é “.cerl”. Ao observar o fonte mostrado em 2.2.1, verifica-se que o mesmo possui todas as três partes utilizadas. Deve-se levar em consideração que não é obrigatório que todas as partes estejam sempre presentes. No caso de uma classe não possuir atributos, por exemplo, a parte relacionada à declaração de atributos não estará presente. No fonte 2.2.1, as três primeiras linhas representam as declarações iniciais do fonte. Na primeira linha é mostrado como declara-se uma classe, utilizando o token “class”. A segunda linha mostra a declaração do construtor, usando-se o token “constructor”, e definindo o método utilizado como construtor. Em seguida são definidos os métodos públicos. Estrutura de um fonte em ooErlang 1 2 3 18 -class(pessoa). -constructor([new/2]). -export([gastar/1, receber/1]). 4 5 attributes. 6 7 8 9 Nome; Nascimento; DinheiroNaCarteira. 10 11 methods. 12 13 14 15 16 new(Nome, Nasc) -> self::Nome = Nome, self::Nascimento = Nasc, self::DinheiroNaCarteira = 0. 17 18 19 gastar(Valor) -> self::DinheiroNaCarteira = self::DinheiroNaCarteira - Valor. 20 21 22 receber(Valor) -> self::DinheiroNaCarteira = self::DinheiroNaCarteira + Valor. Código 2.2.1: Exemplo de uma classe chamada Pessoa em ooErlang Isto ocorre da mesma forma que no Erlang, quando são definidas as funções externas. É importante observar que, diferente do Java, no ooErlang o construtor pode possuir qualquer nome, contanto que seja declarado no inı́cio. Iniciando na linha cinco (5) e terminando na linha nove (9) do fonte 2.2.1 estão declarados os atributos da classe, ou seja, a segunda parte do fonte em ooErlang. Inicialmente é utilizado o token “attributes” seguido de um ponto e em seguida cada atributo é declarado sem possuir um valor inicial. Todos os atributos iniciam com letra maiúscula, são separados por ponto-e-vı́rgula e, após o atributo final, é utilizado outro ponto. Importante verificar que não existe a declaração do tipo de atributo que está sendo definido em ooErlang. A terceira parte do fonte, a declaração e definição dos métodos, está presente no fonte 2.2.1 entre as linhas onze (11) e vinte e dois (22). Semelhante à declaração de atributos, na declaração de métodos é utilizado inicialmente o token “methods” seguido de ponto. O método chamado “new” é o método construtor, definido nas declarações iniciais. Este método recebe dois parâmetros de entrada que são utilizados para inicializar os atributos da classe pessoa. Sempre que um atributo de uma classe for utilizado, a sintaxe empregada necessita Classes com métodos principais e interfaces em ooErlang 19 do token “self”, para que o compilador saiba estar se tratando de um atributo da própria classe. Dessa forma, como mostrado nas linhas 14, 15 e 16 do fonte 2.2.1, os atributos Nome e Nascimento estão sendo inicializados com os parâmetros de entrada do construtor e o atributo DinheiroNaCarteira está recebendo valor inicial igual a zero (0). Ao inicializar os atributos, automaticamente seus tipos são definidos, de acordo com o tipo de dado que determinado atributo está recebendo ao ser inicializado. O atributo Nome, por exemplo, recebe uma cadeia de caracteres assim como o atributo DinheiroNaCarteira recebe um valor numérico. Os métodos mostrados entre as linhas 18 e 22 mostram dois métodos públicos que modificam o valor presente no atributo DinheiroNaCarteira, sendo que um método aumenta o valor neste atributo e o outro o diminui. Ao ser modificado um valor de um atributo da própria classe, após a utilização do token “self” são utilizados dois sinais de dois pontos (“::”) conforme mostrado nas linhas 14, 15, 16, 19 e 22 do fonte 2.2.1. Estes dois sinais de dois pontos seguidos são utilizados com frequência no ooErlang, tanto para referir-se a um atributo da própria classe, quanto para um objeto invocar um método, conforme é explicado nas seções seguintes. 2.3 Classes com métodos principais e interfaces em ooErlang Uma classe em ooErlang com o método principal (main) é a classe na qual são realizados os testes de execução. Seguindo a mesma regra de estruturação de um fonte, a classe principal também irá possuir as declarações iniciais, a definição de atributos e a implementação dos métodos, sejam eles públicos ou privados. Os métodos privados são os não declarados como públicos no inı́cio do fonte. O código mostrado em 2.3.1 mostra a classe principal que utiliza a classe mostrada em 2.2.1. Observa-se que as declarações iniciais estão nas duas primeiras linhas do fonte, declarando a classe e tornando o método principal público. Observa-se que esta classe não possui atributos, portanto, passa-se diretamente à terceira parte, declaração de métodos. A linha quatro (4) mostra o token usado ao serem declarados métodos estáticos: “class methods”. Este token informa ao compilador que os métodos seguintes a ele são estáticos, pertencendo Classes com métodos principais e interfaces em ooErlang 1 2 20 -class(gerenciadorDePessoas). -export([main/0]). 3 4 class_methods. 5 6 7 main() -> PVitor = pessoa::new("Vitor Fernando Pamplona","07/11/1983"), 8 9 PVitor::receber(1000.00), 10 11 PJoao = pessoa::new("Joao da Silva", "18/02/1970"), 12 13 14 PJoao::receber(500.00), PJoao::gastar(100.00). Código 2.3.1: Exemplo de uma classe chamada GerenciadorDePessoas em ooErlang à classe e não a um objeto. Na implementação dos métodos de uma classe em ooErlang, primeiro são definidos os métodos não estáticos, e em seguida os métodos estáticos, de acordo com o token utilizado no inı́cio de cada implementação. Dessa forma, ao ser necessário a uma classe possuir métodos estáticos e não-estáticos, depois da declaração de variáveis, é utilizado o token “methods” seguido de ponto, para definir métodos não-estáticos. Após as referidas definições, é utilizado o token “class methods”, seguido novamente de ponto, para definir os métodos estáticos. O fonte 2.3.1 mostra na linha sete (7) um exemplo de instanciação utilizando o construtor definido no fonte 2.2.1. Assim como no Erlang, no ooErlang uma nova variável é definida apenas por ser escrita, e seu tipo é definido de acordo com o valor que esta variável recebe. No caso da linha 7, é criado um objeto de nome PVitor da classe Pessoa. Este objeto recebe o resultado da invocação do construtor por meio da classe Pessoa. Assim como na manipulação de atributos, na chamada de métodos por objetos é utilizado o sinal duplo de dois pontos (“::”). Para que um objeto chame um método de sua classe, é utilizada a forma “objeto::método(parâmetro1, parâmetro2, ...)”. Assim é criado um objeto do tipo Pessoa, com os atributos Nome e DataDeNascimento sendo passados por parâmetro para o construtor. Dessa forma o objeto PVitor passa ser uma das instâncias da classe Pessoa. A linha nove (9) do fonte 2.3.1 mostra o objeto recém-instanciado PVitor utilizando o Utilização de herança e implementação de interface em ooErlang 21 método para receber dinheiro na carteira, chamado de receber. A linha onze (11) mostra a instanciação de outro objeto, neste caso chamado de PJoao, e as linhas treze (13) e quatorze (14) representam o objeto PJoao invocando os métodos receber e gastar que estão definidos na própria classe Pessoa. A utilização de interfaces no ooErlang também utiliza as regras de estruturação das classes nesta extensão do Erlang. Existem algumas diferenças, que devem ser observadas na implementação de uma interface. Inicialmente, observa-se que interfaces, via de regra geral, não possuem atributos, e seus métodos são apenas assinaturas a serem desenvolvidas pelas classes que as implementam. O fonte mostrado em 2.3.2 mostra um exemplo de interface em ooErlang. 1 2 -interface(figura). -export([calcularArea/0]). 3 4 methods. 5 6 calcularArea(). Código 2.3.2: Exemplo de uma interface chamada Figura em ooErlang A primeira caracterı́stica própria das interfaces em ooErlang está presente na primeira linha, onde é utilizado o token “interface” ao invés do token “class” que é utilizado para classes. A primeira linha, tanto para classes quanto para interfaces, representa o nome da classe ou interface, e deve coincidir com o nome do arquivo a ser implementado. A interface 2.3.2 possui um método público definido na linha dois (2). Verifica-se também que, para definir um método abstrato em uma interface, é necessário apenas inserir um ponto após os parâmetros do referido método, conforme mostrado na linha seis (6) do fonte 2.3.2. 2.4 Utilização de herança e implementação de interface em ooErlang Assim como a linguagem Java, a extensão ooErlang pode ser utilizada para implementar o conceito de herança entre superclasses e subclasses. Desta forma, as classes que herdam Utilização de herança e implementação de interface em ooErlang 22 uma classe superior passam a possuir os mesmos atributos e métodos da superclasse. Também é possı́vel ao se utilizar herança, a redefinição de um método da classe superior. O fonte mostrado em 2.4.1 ilustra um exemplo de uma classe que se utiliza de herança para redefinir um método da classe superior. 1 2 3 -class(soma). -extends(operacaomatematica). -export([calcular/2]). 4 5 methods. 6 7 8 calcular(X,Y) -> X+Y. Código 2.4.1: Exemplo de uma classe chamada Soma em ooErlang Conforme pode ser observado no código-fonte em 2.4.1, as tres primeiras linhas representam as definições iniciais da classe Soma. A segunda linha mostra como uma classe herda os atributos e métodos de uma classe com nı́vel de abstração superior: utilizando-se do token extends. Semelhante ao Java, o ooErlang também utiliza-se deste token para representar herança. Assim, a classe Soma está herdando as caracterı́sticas de uma classe chamada operacaomatematica. Logicamente, o método mostrado na linha sete (7) também é definido na classe operacaomatematica. A classe Soma, conforme pode ser observado no fonte em 2.4.1, não possui atributos. Desta forma, após as declarações iniciais nas três primeiras linhas, passa-se para a definição de métodos. Quando uma classe necessita estender mais de uma superclasse, os nomes das superclasses não são colocados diretamente entre parênteses, mas sim dentro de uma lista. Isto é feito de forma semelhante com a qual são declarados os métodos públicos de uma classe, conforme mostrado no fonte 2.2.1. O fonte mostrado em 2.3.2 representa uma interface em ooErlang que é a abstração para diferentes tipos de figuras. O fonte mostrado em 2.4.2 mostra uma classe chamada Circulo, representando uma das classes que pode implementar esta interface, por se tratar de um tipo de figura geométrica. Esta classe deve, obrigatoriamente, implementar o método calcularArea(), pois trata-se de um método abstrato da interface Figura. Para que o compilador do ooErlang entenda que a classe atual está implementando uma interface, é necessário, nas declarações iniciais, utilizar o token “implements”, seguido do Observações nais da sintaxe do ooErlang 1 2 3 4 23 -class(circulo). -implements(figura). -export([calcularArea/0, new/1]). -constructor([new/1]). 5 6 attributes. 7 8 Raio. 9 10 methods. 11 12 13 new(Raio) -> self::Raio = Raio. 14 15 16 calcularArea() -> 3.14 * self::Raio * self::Raio. Código 2.4.2: Exemplo de uma classe chamada Circulo em ooErlang nome da interface em questão, de acordo com o que está mostrado na segunda linha do fonte 2.4.2. Esta classe possui um atributo necessário para que seja possivel o cálculo de sua área, e este atributo (Raio) é definido entre as linhas 6 e 8. Os métodos estão definidos entre as linhas 10 e 16. Além do método responsável por calcular a área do cı́rculo, também foi definido um construtor na linha 12, sendo que este construtor foi previamente declarado na linha 4. 2.5 Observações finais da sintaxe do ooErlang Conforme demonstrado, as classes e interfaces em ooErlang, possuem três partes bem definidas: declarações iniciais, atributos e métodos. Nas declarações iniciais são definidos o nome da classe ou interface, os métodos públicos (semelhante a como o Erlang define funções externas), heranças de superclasses, implementações de interfaces e métodos construtores, caso a classe defina um próprio construtor. Sempre que for necessário a uma classe possuir um ou mais métodos estáticos, que não necessitem da invocação de um objeto para serem executados, é utilizado o token “class methods” seguido de ponto antes de todos os métodos estáticos. É importante lembrar que, antes da definição dos métodos estáticos, deve-se definir os métodos não-estáticos, iniciados com o token “methods” seguido de ponto. Desta forma o compilador tem a ca- Observações nais da sintaxe do ooErlang 24 pacidade de diferenciar os métodos estáticos dos não-estáticos. Em todas as declarações iniciais, é utilizado um hı́fen no inı́cio e um ponto no final de cada linha. É possı́vel a uma classe em ooErlang implementar uma interface e estender uma superclasse ao mesmo tempo, assim como em Java. Para isso ser possı́vel, basta usar nas declarações iniciais tanto o token “implements” quanto o token “extends” (um por linha). Uma importante caracterı́stica na sintaxe do ooErlang é que, tanto na extensão, quanto na implementação, as classes estendidas e implementadas aparecem dentro de parênteses. Se a classe precisar estender ou implementar mais de uma classe ou interface, elas devem estar dentro de uma lista que esteja dentro dos parênteses. Se uma classe não define um construtor, o compilador do ooErlang define um construtor padrão para a referida classe. Este construtor é utilizado da seguinte forma: “classe::new ()”. Portanto, o construtor padrão de uma classe chama-se “new ()”. O fonte apresentado em 2.5.1 mostra um trecho de uma classe chamada Main, com o exemplo prático da utilização de construtores padrão na instanciação de quatro (4) classes as quais não foram definidos construtores próprios. 1 2 -class(main). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 10 11 main() -> Transp = transporte::new_(), Aviao = aviao::new_(), Navio = navio::new_(), Onibus = onibus::new_(), (...) Código 2.5.1: Exemplo de uma classe chamada Main em ooErlang Pode-se verificar nesta classe que existe apenas o método principal, chamado “main”, e que esta classe também não possui atributos. Para as quatro instanciações de diferentes tipos de objetos, mostradas nas linhas 7, 8, 9 e 10, é utilizado o construtor padrão. Percebese que, sempre que o construtor padrão for utilizado, não é passado nenhum parâmetro de entrada na instanciação do objeto. Os atributos de uma classe instanciada com um construtor padrão são inicializados com valores nulos. Capı́tulo 3 Padrões de Projeto de Criação Neste capı́tulo são analisados os cinco (5) padrões de projeto de criação definidos por [Gamma et al., 1994]. Para cada um dos padrões de criação explicados, é estudado o exemplo prático utilizado no mesmo, sendo feitas comparações de implementações dos códigos-fonte em Java e ooErlang, das principais classes modeladas no respectivo exemplo. Esta análise comparativa visa verificar a extensão ooErlang no que diz respeito à sua utilização na implementação dos padrões de projeto de criação. 3.1 Conceitos Iniciais Padrões de projeto de criação abstraem o processo de instanciação [Gamma et al., 1994]. Desta forma, utilizando os padrões de criação, é possı́vel criar sistemas que independem da criação, composição e representação dos diferentes objetos. Logo, torna-se menos trabalhoso estender ou fazer manutenção em um projeto que utilize algum ou vários padrões de criação em conjunto. Uma classe de um padrão de criação utiliza herança para variar a classe que é instanciada, ao passo que um objeto de um padrão de criação irá delegar a instanciação a outro objeto [Gamma et al., 1994]. É perceptı́vel que a utilização dos padrões de criação torna-se necessária quando o sistema precisa ser desenvolvido utilizando principalmente a composição de objetos. Padrões de criação tornam-se importantes quando sistemas evoluem Padrão Abstract Factory 26 passando a depender mais de composição de objetos do que herança de classes [Gamma et al., 1994]. De acordo com [Gamma et al., 1994], existem 5 (cinco) padrões de projetos de criação, sendo eles: Abstract Factory, Builder, Factory Method, Prototype e Singleton. Esta é a ordem em que os padrões são tratados, demonstrando a utilização dos mesmos com a extensão orientada a objetos do Erlang, ooErlang. Todos os códigos-fonte deste capı́tulo podem ser encontrados em [GitHub, 2008]. 3.2 Padrão Abstract Factory De acordo com sua definição, o Abstract Factory tem como objetivo “Prover uma interface para criar famı́lias de objetos relacionados ou dependentes sem especificar suas classes concretas.” [Gamma et al., 1994]. Este padrão tem como objetivo facilitar a utilização de famı́lias de objetos por meio de interfaces utilizadas para criação dos objetos especificados pelo usuário. O padrão Abstract Factory é recomendado e utilizado quando necessita-se de um projeto no qual haja independência entre a criação, composição e representação dos objetos em relação ao sistema como um todo. Além disso, pode ser usado quando um sistema puder ser configurado com uma de múltiplas famı́lias de produtos e também quando seja necessário prover uma biblioteca de produtos, necessitando revelar apenas suas interfaces e não suas implementações. 3.2.1 Exemplo utilizado para este padrão No exemplo que foi utilizado para implementar a aplicação na prática do padrão Abstract Factory, desenvolveu-se um sistema para gerenciar uma loja de pizzas. Este exemplo foi extraı́do de [Freeman et al., 2004]. Considera-se neste exemplo que existem duas franquias desta pizzaria, uma em Nova Iorque e outra em Chicago. Cada franquia possui os mesmos sabores de pizza, porém com ingredientes diferentes. Pode-se perceber que existem famı́lias de produtos neste cenário. Cada sabor de pizza vai possuir diferentes ingredientes, dependendo da franquia da loja de pizzas. Portanto, a Padrão Abstract Factory 27 forma de utilizar o padrão Abstract Factory é decompondo a criação de cada objeto pizza em “fábricas” de ingredientes. Isto significa dizer que para cada sabor de pizza de Nova Iorque vai existir uma classe somente para a fábrica de ingredientes. O mesmo é válido para a franquia de pizzas de Chicago. Portanto, na modelagem em Java vai existir uma interface chamada PizzaIngredientFactory e as classes que irão implementar essas interfaces serão justamente as que funcionarão como fábricas de ingredientes de cada franquia da pizzaria, no caso NYPizzaIngredientFactory (para os ingredientes das pizzas de Nova Iorque) e ChicagoPizzaIngredientFactory (para as pizzas de Chicago). Existem famı́lias de ingredientes para cada tipo de ingrediente. Os tipos de ingredientes são os seguintes: “Veggies”, que são os tipos de vegetais, “Clams”, para os tipos de ameijoas, “Sauce” para os tipos de molhos existentes, “Cheese” para os tipos de queijo, “Pepperoni”, no caso do pepperoni e “Dough”, para definir como vai ser a massa da pizza. Foram utilizados nomes especı́ficos para as classes que representam as duas lojas da pizzaria. As franquias da pizzaria passaram a possuir os seguintes nomes: NewYorkPizzaStore e ChicagoPizzaStore. Os sabores de pizza de cada franquia, por sua vez, possuem os nomes: CheesePizza, VeggiePizza, ClamPizza e PepperoniPizza. Para entender melhor este cenário, a figura 3.1 mostra o diagrama de classes simplificado desta modelagem. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Desta forma é possı́vel observar a utilização do padrão Abstract Factory na modelagem das franquias da loja de pizzas. Cada grupo de ingredientes foi modelado para possuir uma interface que é implementada pelos respectivos ingredientes. 3.2.2 Comparação das implementações em Java e ooErlang Conforme explicado anteriormente, cada tipo de ingrediente é modelado para ser uma interface a ser implementada por todos os respectivos ingredientes. Por exemplo, um tipo de ingrediente é o queijo, ou “Cheese”, portanto, vai haver uma interface chamada Cheese, e as classes que a irão implementar são os tipos de queijo, ou seja, “MozzarellaCheese” (queijo mussarela), “ParmesanCheese” (queijo parmesão) e “ReggianoCheese” (queijo parmesão reggiano). Padrão Abstract Factory 28 Figura 3.1: Diagrama de classes do exemplo para o padrão Abstract Factory. Para comparar os fontes gerados, são verificadas as implementações da interface Cheese, tanto em Java quanto em ooErlang, analisando assim as principais diferenças entre ambas. Padrão Abstract Factory 29 Verificamos no código 3.2.1, a implementação em Java desta interface, assim como no código 3.2.2 a implementação similar em ooErlang. 1 2 3 public interface Cheese { public String toString(); } Código 3.2.1: Interface Cheese em Java 1 2 -interface(cheese). -export([to_string/0]). 3 4 methods. 5 6 to_string(). Código 3.2.2: Interface Cheese em ooErlang É possı́vel observar pequenas diferenças de implementação entre ambas as abordagens. É evidente que a forma de escrita do ooErlang é bastante próxima e similar à do Java. Semelhante à essa interface, também existem as interfaces Pepperoni, Dough, Veggies, Clams e Sauce, conforme abordadas anteriormente. O padrão Abstract Factory permite que, por meio das classes ChicagoPizzaIngredientFactory e NYPizzaIngredientFactory, seja possı́vel definir quais sabores pertencem às respectivas franquias. Eis a vantagem de utilizar este padrão. Dessa forma, as famı́lias de algoritmos tornam-se desacopladas do resto das implementações, tornando a inclusão de possı́veis novos ingredientes uma tarefa menos impactante para o resto da modelagem. Os códigos 3.2.3 e 3.2.4 mostram as implementações da interface PizzaIngredientFactory em Java e ooErlang respectivamente. Esta interface é implementada pelas duas distintas franquias da pizzaria, NYPizzaIngredientFactory e ChicagoPizzaIngredientFactory. E são justamente estas implementações que definem os tipos de ingredientes para cada diferente tipo de sabor de pizza, de acordo com o sabor escolhido pelo cliente. A classe principal (que possui o método Main), é a classe PizzaTestDrive. Tanto em Java quanto em ooErlang, ambas possuem o mesmo comportamento. Os códigos 3.2.5 e 3.2.6 mostram a implementação desta classe em Java e ooErlang respectivamente. As Padrão Abstract Factory 1 30 public interface PizzaIngredientFactory { 2 public public public public public public 3 4 5 6 7 8 Dough createDough(); Sauce createSauce(); Cheese createCheese(); Veggies[] createVeggies(); Pepperoni createPepperoni(); Clams createClam(); 9 10 } Código 3.2.3: Interface PizzaIngredientFactory em Java 1 2 3 -interface(pizzaIngredientFactory). -export([create_dough/0, create_sauce/0, create_cheese/0]). -export([create_veggies/0, create_pepperoni/0, create_clam/0]). 4 5 methods. 6 7 8 9 10 11 12 create_dough(). create_sauce(). create_cheese(). create_veggies(). create_pepperoni(). create_clam(). Código 3.2.4: Interface PizzaIngredientFactory em ooErlang figuras 3.2 e 3.3 mostram a execução deste fonte, sendo dessa forma possı́vel verificar e comparar o resultado de implementação das duas abordagens. Observam-se algumas diferenças na execução dos fontes em Java e ooErlang. Em Java a execução em linha de comando ocorre semelhante a qualquer outro programa. Já em ooErlang, é necessário primeiramente executar o programa Erlang, e em seguida chamar o método desejado passando a classe à qual este método pertence, de forma semelhante com a qual se chama uma função de um módulo em Erlang. Dessa forma, espera-se que este fonte execute similarmente ao fonte em Java. Verificando as implementações mostradas, observa-se uma grande semelhança entre as formas de sintaxe do Java e do ooErlang. Podemos aplicar este padrão em ooErlang da mesma forma que este padrão já é utilizado em Java. Quando se tem uma modelagem que possui diversas famı́lias de produtos, comportamentos ou caracterı́sticas, este padrão poderá ser utilizado. A facilidade de implementação em ooErlang demonstra a flexibilidade desta extensão do Padrão Builder 1 31 public class PizzaTestDrive { 2 public static void main(String[] args) { PizzaStore nyStore = new NYPizzaStore(); PizzaStore chicagoStore = new ChicagoPizzaStore(); 3 4 5 6 Pizza pizza = nyStore.orderPizza("cheese"); System.out.println("Ethan ordered a " + pizza + "\n"); 7 8 9 pizza = chicagoStore.orderPizza("cheese"); System.out.println("Joel ordered a " + pizza + "\n"); 10 11 12 pizza = nyStore.orderPizza("clam"); System.out.println("Ethan ordered a " + pizza + "\n"); 13 14 15 pizza = chicagoStore.orderPizza("clam"); System.out.println("Joel ordered a " + pizza + "\n"); 16 17 18 pizza = nyStore.orderPizza("pepperoni"); System.out.println("Ethan ordered a " + pizza + "\n"); 19 20 21 pizza = chicagoStore.orderPizza("pepperoni"); System.out.println("Joel ordered a " + pizza + "\n"); 22 23 24 pizza = nyStore.orderPizza("veggie"); System.out.println("Ethan ordered a " + pizza + "\n"); 25 26 27 pizza = chicagoStore.orderPizza("veggie"); System.out.println("Joel ordered a " + pizza + "\n"); 28 29 } 30 31 } Código 3.2.5: Classe principal PizzaTestDrive em Java Erlang para problemas que envolvam situações de acoplamento forte entre classes. Quando se necessite utilizar uma solução com o intuito de se obter uma modelagem desacoplada, para um problema de famı́lias de classes e subclasses, é possivel utilizar, da mesma forma que se realiza no Java, uma solução em ooErlang, resolvendo o problema similarmente. 3.3 Padrão Builder De acordo com sua definição, o padrão Builder tem por objetivo “Separar a construção de um objeto complexo de sua representação, para que o mesmo processo de construção possa criar representações diferentes” [Gamma et al., 1994]. Desta forma, a estrutura de construção do objeto estará encapsulada, ou seja, não visı́vel ao usuário, estando disponı́vel Padrão Builder 1 2 32 -class(pizzaTestDrive). -export([main/0]). 3 4 class_methods. 5 6 7 8 main() -> NyStore = nyPizzaStore::new_(), ChicagoStore = chicagoPizzaStore::new_(), 9 10 11 12 Pizza1 = NyStore::order_pizza("cheese", nyPizzaStore), io:format("Ethan ordered a "), Pizza1::to_string(nyPizzaStore), 13 14 15 16 Pizza2 = ChicagoStore::order_pizza("cheese", chicagoPizzaStore), io:format("Joel ordered a "), Pizza2::to_string(chicagoPizzaStore), 17 18 19 20 Pizza3 = NyStore::order_pizza("clam", nyPizzaStore), io:format("Ethan ordered a "), Pizza3::to_string(nyPizzaStore), 21 22 23 24 Pizza4 = ChicagoStore::order_pizza("clam", chicagoPizzaStore), io:format("Joel ordered a "), Pizza4::to_string(chicagoPizzaStore), 25 26 27 28 Pizza5 = NyStore::order_pizza("pepperoni", nyPizzaStore), io:format("Ethan ordered a "), Pizza5::to_string(nyPizzaStore), 29 30 31 32 Pizza6 = ChicagoStore::order_pizza("pepperoni", chicagoPizzaStore), io:format("Joel ordered a "), Pizza6::to_string(chicagoPizzaStore), 33 34 35 36 Pizza7 = NyStore::order_pizza("veggie", nyPizzaStore), io:format("Ethan ordered a "), Pizza7::to_string(nyPizzaStore), 37 38 39 40 Pizza8 = ChicagoStore::order_pizza("veggie", chicagoPizzaStore), io:format("Joel ordered a "), Pizza8::to_string(chicagoPizzaStore). Código 3.2.6: Classe principal PizzaTestDrive em ooErlang apenas a interface de construção dos referidos objetos. Usualmente, o padrão Builder é melhor aplicado em situações em que o algoritmo responsável por criar objetos complexos devem ser independentes das partes que constituem o objeto e a forma com que o mesmo é construı́do. Este padrão é também bastante utilizado quando o processo de construção deverá permitir diferentes representações do objeto que está sendo construı́do. Padrão Builder 33 Figura 3.2: Execução do exemplo para o padrão Abstract Factory em Java. 3.3.1 Exemplo utilizado para este padrão No exemplo utilizado temos um cenário de construção de pizzas. Neste novo cenário apresentam-se dois tipos de pizza apenas, Hawaiian Pizza (pizza havaina) e Spicy Pizza (pizza apimentada). Neste exemplo, retirado de [Making, 2013], cada pizza possui uma quantidade certa de ingredientes, sendo que os ingredientes são distintos para ambos os sabores. De acordo com o padrão, dependendo do pedido do cliente, vai ser construı́da uma pizza do tipo havaiana ou apimentada. Mas em ambos os casos, será utilizada uma classe chamada “Waiter”, que faz o papel do construtor, estando separado na construção da representação dos objetos pizza existentes. Desta forma, ambos os sabores são construı́dos pelo mesmo processo, construı́ndo diferentes pizzas com diferentes ingredientes. A figura 3.4 mostra um diagrama simplificado deste exemplo para melhor entendimento do cenário. Padrão Builder 34 Figura 3.3: Execução do exemplo para o padrão Abstract Factory em ooErlang. Sempre que um sabor de pizza for escolhido pelo usuário, o mesmo será criado por meio da classe “Waiter”. Esta classe irá primeiramente instanciar um objeto da classe “Pizza”, recebendo um tipo de pizza designada pelo usuário, utilizando em seguida um método chamado “constructPizza”, responsável por inserir e construir os ingredientes especı́ficos para a pizza desejada. Este mesmo método de construção é utilizado para qualquer tipo de pizza, assim como será utilizado se houver inclusão de algum novo sabor de pizza. 3.3.2 Comparação das implementações em Java e ooErlang Verificando inicialmente a classe “Pizza”, tanto em Java quanto em ooErlang, observa-se que a mesma é uma classe que irá ser herdada pelos dois sabores de pizza definidos para o exemplo dado. Verifica-se também que cada pizza possui três (3) ingredientes principais, que são “Dough” (massa), “Sauce” (molho), e “Toppings”, referente aos ingredientes extras de cada pizza. Os códigos 3.3.1 e 3.3.2 mostram as respectivas implementações desta classe em Java e ooErlang. Padrão Builder 35 Figura 3.4: Diagrama de classes do exemplo para o padrão Builder. 1 2 3 4 public class Pizza { private String dough = ""; private String sauce = ""; private String topping = ""; 5 public void setDough(String dough) { this.dough = dough; } public void setSauce(String sauce) { this.sauce = sauce; } public void setTopping(String topping) { this.topping = topping; } 6 7 8 9 public void showIngredients() { System.out.println("Ingredients: " + dough + ", " + sauce + ", " + topping); } 10 11 12 13 14 15 } Código 3.3.1: Classe Pizza em Java A classe “Waiter”, conforme dito anteriormente, é responsável por iniciar a construção propriamente dita dos objetos do tipo “Pizza”. Esta classe deve iniciamente receber o tipo de pizza a ser construı́do, e em seguida procede na construção da mesma, inserindo os di- Padrão Builder 1 2 36 -class(pizza). -export([set_dough/1, set_sauce/1, set_topping/1, show_ingredients/0]). 3 4 attributes. 5 6 7 8 Dough; Sauce; Topping. 9 10 methods. 11 12 13 set_dough(Dough) -> self::Dough = Dough. 14 15 16 set_sauce(Sauce) -> self::Sauce = Sauce. 17 18 19 set_topping(Topping) -> self::Topping = Topping. 20 21 22 23 show_ingredients() -> io:format("Ingredients: ~p, ~p, ~p~n", [self::Dough, self::Sauce, self::Topping]). Código 3.3.2: Classe Pizza em ooErlang ferentes tipos de sabores em cada pizza, por meio dos métodos “createNewPizzaProduct()”, “buildDough()”, “buildSauce()” e “buildTopping()”. Os códigos 3.3.3 e 3.3.4 mostram com mais detalhes estas implementações. 1 2 public class Waiter { private PizzaBuilder pizzaBuilder; 3 public void setPizzaBuilder(PizzaBuilder pb) { pizzaBuilder = pb; } public Pizza getPizza() { return pizzaBuilder.getPizza(); } 4 5 6 public void constructPizza() { pizzaBuilder.createNewPizzaProduct(); pizzaBuilder.buildDough(); pizzaBuilder.buildSauce(); pizzaBuilder.buildTopping(); } 7 8 9 10 11 12 13 } Código 3.3.3: Classe Waiter em Java A classe principal, “BuilderExample”, demonstra uma forma com a qual o exemplo pode ser utilizado. São instanciados dois tipos de pizza, cada um para um diferente sabor. Para Padrão Builder 1 2 37 -class(waiter). -export([set_pizza_builder/1, get_pizza/0, construct_pizza/0]). 3 4 attributes. 5 6 PizzaBuilder. 7 8 methods. 9 10 11 set_pizza_builder(Pb) -> self::PizzaBuilder = Pb. 12 13 14 15 16 get_pizza() -> Temp = self::PizzaBuilder, Return = Temp::get_pizza(), Return. 17 18 19 20 21 22 23 construct_pizza() -> Temp = self::PizzaBuilder, Temp::create_new_pizza_product(), Temp::build_dough(), Temp::build_sauce(), Temp::build_topping(). Código 3.3.4: Classe Waiter em ooErlang cada construção da pizza é utilizada uma instanciação da classe “Waiter”, sendo que a diferenciação nas construções, ou seja, os sabores de cada pizza estão encapsulados para o usuário. As construções de cada especı́fico sabor são chamados pela classe “Waiter”. Conforme pode ser observado nos códigos 3.3.5 e 3.3.6, as implementações em Java e ooErlang, respectivamente, são bastante semelhantes. Na sintaxe do ooErlang, muitas caracterı́sticas do Erlang estão presentes, com algumas diferenças que são especı́ficas da extensão do Erlang para utilização da orientação à objetos nesta linguagem. Em Java não existem dificuldades de entendimento, pois a sintaxe já é bastante conhecida, com a ressalva de estar sendo utilizada na implementação de um padrão de projeto. As figuras 3.5 e 3.6 mostram a execução do exemplo do padrão “Builder” nas implementações feitas em Java e ooErlang respectivamente. Conforme já foi dito anteriormente, inicialmente é instanciado e construı́do um objeto do tipo “HawaiianPizza”, em seguida, o mesmo procedimento é realizado para um objeto do tipo “SpicyPizza”. Ambos tem construção similar, porém resultados diferentes. Verificou-se que é possı́vel a implementação e a execução de um exemplo que se utiliza Padrão Builder 1 2 3 4 5 6 7 38 public class BuilderExample { public static void main(String[] args) { Waiter waiter = new Waiter(); PizzaBuilder hawaiianPizzaBuilder = new HawaiianPizzaBuilder(); PizzaBuilder spicyPizzaBuilder = new SpicyPizzaBuilder(); 8 System.out.println("Preparing to build Hawaiian Pizza..."); waiter.setPizzaBuilder( hawaiianPizzaBuilder ); waiter.constructPizza(); 9 10 11 12 Pizza pizza1 = waiter.getPizza(); System.out.println(hawaiianPizzaBuilder); pizza1.showIngredients(); 13 14 15 16 System.out.println("\nPreparing to build Spicy Pizza..."); waiter.setPizzaBuilder(spicyPizzaBuilder); waiter.constructPizza(); 17 18 19 20 Pizza pizza2 = waiter.getPizza(); System.out.println(spicyPizzaBuilder); pizza2.showIngredients(); 21 22 23 } 24 25 } Código 3.3.5: Classe principal BuilderExample em Java do padrão Builder em ooErlang, obtendo, assim, um resultado semelhante ao obtido com a execução do mesmo exemplo implementado em Java. A extensão ooErlang mostrou-se apta a realizar uma implementação similar e com os mesmos resultados que já tinham sido verificados em Java. Na utilização deste padrão, é possı́vel variar a representação interna de um produto, neste caso a pizza. Dependendo do sabor escolhido, os tipos de ingredientes variam. Eles são a representação interna do objeto neste exemplo. A interface deste padrão é responsável por esconder a estrutura interna do produto, no caso, os objetos do tipo pizza. Desta forma a construção e a representação dos produtos foram isoladas, com o encapsulamento do processo de construção das pizzas. Padrão Factory Method 1 2 39 -class(builderExample). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 main() -> Waiter = waiter::new_(), HawaiianPizzaBuilder = hawaiianPizzaBuilder::new_(), SpicyPizzaBuilder = spicyPizzaBuilder::new_(), 10 io:format(" Preparing to build Hawaiian Pizza...~n"), Waiter::set_pizza_builder(HawaiianPizzaBuilder), Waiter::construct_pizza(), 11 12 13 14 Pizza1 = Waiter::get_pizza(), io:format("~p ", [HawaiianPizzaBuilder::to_string()]), Pizza1::show_ingredients(), 15 16 17 18 io:format("~n Preparing to build Spicy Pizza...~n"), Waiter::set_pizza_builder(SpicyPizzaBuilder), Waiter::construct_pizza(), 19 20 21 22 Pizza2 = Waiter::get_pizza(), io:format("~p ", [SpicyPizzaBuilder::to_string()]), Pizza2::show_ingredients(). 23 24 25 Código 3.3.6: Classe principal BuilderExample em ooErlang 3.4 Padrão Factory Method De acordo com sua definição inicial, o padrão Factory Method tem como objetivo: “Definir uma interface para criar um objeto mas deixar que subclasses definam que classe instanciar. Factory Method permite que uma classe delegue a responsabilidade de instanciamento às subclasses” [Gamma et al., 1994]. Factory Method pode ser confundido com o padrão Abstract Factory, mas sua diferença está no fato de delegar a instanciação dos objetos diretamente para as subclasses. Este padrão pode ser bem aplicado a situações e problemas em que uma classe não pode antecipar que tipo de objetos deve criar, assim como precisa delegar às subclasses a responsabilidade de especificar os objetos que serão instanciados. O padrão Factory Method também permite, dependendo dos objetivos da implementação, que seja possı́vel localizar a subclasse que irá ser a responsável por instanciar determinado objeto. Padrão Factory Method 40 Figura 3.5: Execução do exemplo para o padrão Builder em Java. 3.4.1 Exemplo utilizado para este padrão No exemplo que foi implementado para este padrão, retirado de [Freeman et al., 2004], houve novamente uma implementação de uma pizzaria. Neste caso, o cenário é parecido com o utilizado no padrão Abstract Factory, com a diferença de que é menos complexo, pois não existem as famı́lias de ingredientes para cada sabor diferente de pizza, apesar de mesmos sabores de pizzas em franquias diferentes possuı́rem ingredientes diferentes. A pizzaria possui ainda suas duas franquias de pizza, uma em Nova Iorque, representada pela classe NYPizzaStore e outra em Chicago, representada pela classe ChicagoPizzaStore. A idéia central é a de não instanciar os objetos representando cada sabor de pizza de diferentes franquias diretamente, e sim utilizar uma classe intermediária que receberá o tipo de pizza a ser instanciada e criada, assim como a franquia da qual a mesma pertence para que este objeto possa ser criado pelas subclasses referentes ao sabor selecionado. Padrão Factory Method 41 Figura 3.6: Execução do exemplo para o padrão Builder em ooErlang. Portanto, para este exemplo existe uma classe chamada PizzaStore, que possui a interface necessária para que se possa criar qualquer sabor de pizza desejado, por meio do método abstrato createPizza(). Este método é implementado em duas outras subclasses, chamadas NYPizzaStore (relacionada com os sabores de pizza de Nova Iorque) e ChicagoPizzaStore (para criar os sabores de Chicago). Portanto, sempre a criação de cada pizza vai ser solicitada na classe principal, PizzaTestDrive, que, por sua vez, repassa esta responsabilidade de criação às subclasses. Existe também a classe Pizza, que possui todos os atributos e métodos, representando genericamente cada objeto pizza a ser criado. Nesta modelagem é possı́vel perceber que o código sujeito a modificações é isolado do código que não muda. Os métodos da classe Pizza por exemplo, que não são passı́veis de modificação, estão separados dos tipos de pizza, que podem modificar-se com o tempo (sabores podem ser incluı́dos e retirados com o tempo). A figura 3.7 mostra um diagrama de classes simplificado para melhor entendimento do cenário. Pode-se observar que as classes NYPizzaStore e ChicagoPizzaStore herdam os métodos da classe PizzaStore, que no caso, representa a pizzaria como um todo. As subclasses Padrão Factory Method 42 Figura 3.7: Diagrama de classes do exemplo para o padrão Factory Method. são as franquias desta pizzaria, cada uma com suas caracterı́sticas diferentes de sabores. Cada subclasse possui o método createPizza, responsável por iniciar a criação da pizza propriamente dita. 3.4.2 Comparação das implementações em Java e ooErlang Tanto em Java quanto em ooErlang, a mesma estrutura foi criada, de acordo com o diagrama de classes simplificado mostrado na figura 3.7. Verificando o fonte da classe PizzaStore, conforme os códigos em 3.4.1 e 3.4.2 que mostram as implementações em Java e ooErlang respectivamente, verifica-se que a criação da pizza é repassada para as subclasses Padrão Factory Method 43 enquanto que o preparo permanece nesta classe. 1 public abstract class PizzaStore { 2 abstract Pizza createPizza(String item); 3 4 public Pizza orderPizza(String type) { Pizza pizza = createPizza(type); System.out.println("--- Making a " + pizza.getName() + " ---"); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } 5 6 7 8 9 10 11 12 13 14 } Código 3.4.1: Classe PizzaStore em Java 1 2 -class(pizzaStore). -export([create_pizza/1, order_pizza/2]). 3 4 methods. 5 6 create_pizza(Item) -> null. 7 8 9 10 order_pizza(Type, String) -> Object = {String, ObjectID}, Pizza = Object::create_pizza(Type), 11 12 13 14 15 16 17 io:format("--- Making a ~p --- ~n", [Pizza::get_name()]), Pizza::prepare(), Pizza::bake(), Pizza::cut(), Pizza::box(), Pizza. Código 3.4.2: Classe PizzaStore em ooErlang Desta forma o código que não é sujeito a modificações, permanece isolado do código que pode modificar-se com o tempo. A classe Pizza possui as implementações dos métodos que tratam dos procedimentos para finalização do processo de construção da pizza, mas isto ocorre apenas depois da pizza de fato já ter recebido seus ingredientes. Os códigos 3.4.3 e 3.4.4 mostram a implementação da classe Pizza em Java e ooErlang, respectivamente. Existem algumas diferenças próprias entre as sintaxes do Java e ooErlang, porém a utilização dos atributos e dos métodos é a mesma. As classes NYPizzaStore e Chicago- Padrão Factory Method 1 2 3 4 5 44 public abstract class Pizza { String name; String dough; String sauce; ArrayList toppings = new ArrayList(); 6 7 8 9 10 11 12 13 14 15 void prepare() { System.out.println("Preparing " + name); System.out.println("Tossing dough..."); System.out.println("Adding sauce..."); System.out.println("Adding toppings: "); for (int i = 0; i < toppings.size(); i++) { System.out.println(" " + toppings.get(i)); } } 16 17 18 19 void bake() { System.out.println("Bake for 25 minutes at 350"); } 20 21 22 23 void cut() { System.out.println("Cutting the pizza into diagonal slices"); } 24 25 26 27 void box() { System.out.println("Place pizza in official PizzaStore box"); } Código 3.4.3: Classe Pizza em Java PizzaStore são similares em suas implementações, com a diferença de que cada uma trata dos sabores para suas próprias lojas. Cada franquia possui os mesmos sabores sendo que, o que diferencia os sabores de uma loja para a outra são os ingredientes utilizados para o preparo das pizzas. Os códigos 3.4.5 e 3.4.6 mostram as respectivas implementações em Java e ooErlang da classe NYPizzaStore. Na execução deste exemplo é utilizada a classe PizzaTestDrive. Nesta classe, são inicialmente criadas instâncias das classes NYPizzaStore e ChicagoPizzaStore, para que sejam as responsáveis por tratar dos pedidos dos clientes de pizza. Em seguida são instanciadas várias pizzas, para vários sabores de pizza nas duas franquias da pizzaria. O método utilizado para iniciar o preparo das pizzas, conforme já visto anteriormente, é o orderPizza(), que recebe como parâmetro o sabor da pizza a ser feita. A franquia que irá preparar a referida pizza é justamente o objeto da classe desta franquia que já foi previamente instanciado. Se, por exemplo, uma pizza de queijo (Che- Padrão Factory Method 1 2 45 -class(pizza). -export([prepare/0, bake/0, cut/0, box/0, get_name/0]). 3 4 attributes. 5 6 7 8 9 Name; Dough; Sauce; Toppings. 10 11 methods. 12 13 14 15 16 17 18 19 prepare() -> io:format("Preparing ~p ~n", [self::Name]), io:format("Tossing dough... ~n"), io:format("Adding Sauce... ~n"), io:format("Adding toppings: ~n"), Tops = self::Toppings, prepare_aux(Tops). 20 21 22 23 24 prepare_aux([]) -> null; prepare_aux([Top|Toppings]) -> io:format(" ~p~n", [Top]), prepare_aux(Toppings). 25 26 27 bake() -> io:format("Bake for 25 minutes at 350 ~n"). 28 29 30 cut() -> io:format("Cutting the pizza into diagonal slices ~n"). 31 32 33 box() -> io:format("Place pizza in official PizzaStore box ~n"). Código 3.4.4: Classe Pizza em ooErlang esePizza) da franquia de Nova Iorque precisar ser preparada, então a instância da classe NYPizzaStore irá chamar o método orderPizza(“cheese”), onde o sabor da referida pizza é passado como parâmetro. As figuras 3.8 e 3.9 mostram a execução em Java e ooErlang deste exemplo. A utilização do padrão Factory Method em ooErlang mostrou-se com resultados satisfatórios, uma vez que foi possı́vel de ser implementada, assim como sua execução mostrou semelhante resultado. A comprovação experimental da execução igual em Java e em ooErlang valida esta extensão do Erlang para ser utilizada em situações nas quais este padrão precise ser utilizado. Na utilização deste padrão, percebe-se que o código trata de forma bem definida a Padrão Factory Method 1 46 public class NYPizzaStore extends PizzaStore { 2 Pizza createPizza(String item) { if (item.equals("cheese")) { return new NYStyleCheesePizza(); } else if (item.equals("veggie")) { return new NYStyleVeggiePizza(); } else if (item.equals("clam")) { return new NYStyleClamPizza(); } else if (item.equals("pepperoni")) { return new NYStylePepperoniPizza(); } else return null; } 3 4 5 6 7 8 9 10 11 12 13 14 } Código 3.4.5: Classe NYPizzaStore em Java 1 2 3 -class(nyPizzaStore). -extends(pizzaStore). -export([create_pizza/1]). 4 5 methods. 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 create_pizza(Item) -> if (Item == "cheese") -> Pizza = nyStyleCheesePizza::new(), Pizza; (Item == "veggie") -> Pizza = nyStyleVeggiePizza::new(), Pizza; (Item == "clam") -> Pizza = nyStyleClamPizza::new(), Pizza; (Item == "pepperoni") -> Pizza = nyStylePepperoniPizza::new(), Pizza; true -> null end. Código 3.4.6: Classe NYPizzaStore em ooErlang interface de utilização do cliente, assim como pode trabalhar com qualquer objeto concreto definido pelo usuário. Criar um objeto dentro de uma classe utilizando este padrão é uma solução geralmente mais flexı́vel do que criar um objeto diretamente. Padrão Prototype 47 Figura 3.8: Execução do exemplo para o padrão Factory Method em Java. 3.5 Padrão Prototype Segundo sua definição, este padrão tem por objetivo “Especificar os tipos de objetos a serem criados usando uma instância como protótipo e criar novos objetos ao copiar este protótipo [Gamma et al., 1994]. Isto significa dizer que este padrão tem como objetivo principal facilitar a criação de novos objetos, em situações onde a instanciação simples de novos objetos de determinada classe seja demasiado custosa ou complexa para o sistema como um todo. Este padrão pode ser utilizado quando temos um sistema que deve ser independente de como seus produtos são criados, compostos e representados. Também pode ser usado quando as classes a serem instanciadas são especificadas em tempo de execução, ou quando as instâncias de uma classe podem possuir apenas pequenas combinações de estados diferentes. Neste caso é mais conveniente criar alguns protótipos para estes estados, clonando-os Padrão Prototype 48 Figura 3.9: Execução do exemplo para o padrão Factory Method em ooErlang. ao invés de instanciar os objetos. 3.5.1 Exemplo utilizado para este padrão O exemplo utilizado foi retirado de [Eriksson, 2013], tratando-se de uma aplicação em que dois tipos de objetos podem ser intanciados: Cachorros (Dog), e Pessoas (Person). Neste exemplo foi utilizada uma interface chamada Prototype, responsável por possuir o método que realiza o processo de criar um novo objeto com as mesmas caracterı́sticas de um objeto já existente, seja ele um cachorro ou uma pessoa. Dessa forma, caso seja necessário criar vários objetos do tipo cachorro, assim como criar vários objetos do tipo pessoa, com pequenas diferenças de caracterı́sticas entre eles, ou seja, com poucas variações, é possı́vel fazer isso apenas instanciando os tipos básicos, e em seguida pode-se realizar a clonagem deles para que se tenha um processo de criação de objetos mais simplificado, utilizando-se dos protótipos existentes. A figura 3.10 mostra um diagrama de classes simplificado para melhor entendimento deste exemplo. Portanto, é possı́vel observar que existem duas classes principais, Dog e Person. Cada classe possui, para simplificação do exemplo e facilidade na compreensão, uma caracterı́stica Padrão Prototype 49 Figura 3.10: Diagrama de classes do exemplo para o padrão Prototype. singular. No caso da classe Dog, essa caracterı́stica é o som de seu latido. Em relação à classe Person, a caracterı́stica é o nome da referida pessoa. Então cada objeto da classe Dog vai possuir um som de latido especı́fico, assim como cada objeto da classe Person vai possuir um nome próprio. 3.5.2 Comparação das implementações em Java e ooErlang Tanto na implementação deste exemplo em Java quanto em ooErlang, foram utilizadas quatro (4) classes: Dog e Person, que são as classes referentes aos tipos diferentes de objetos, Prototype, interface que possui o método responsável por clonar objetos na criação de novos objetos e a classe Demo, tratando-se da classe principal deste exemplo, possuindo o método Main(). As classes Dog e Person são similares em suas implementações, sendo que possuem o método construtor para instanciar novas classes e um método necessário para criar novas instâncias clonando as caracterı́sticas de um objeto já existente, ou seja, construindo um Padrão Prototype 50 novo objeto utilizando-se de caracterı́sticas de outro objeto. É dessa forma que o padrão Prototype é utilizado. Os códigos 3.5.1 e 3.5.2 mostram as implementações da classe Person em Java e ooErlang, respectivamente. 1 public class Person implements Prototype { 2 String name; 3 4 public Person(String name) { this.name = name; } 5 6 7 8 public Prototype doClone() { return new Person(name); } 9 10 11 12 public String toString() { return "This person is named " + name; } 13 14 15 16 } Código 3.5.1: Classe Person em Java 1 2 3 4 -class(person). -implements(prototype). -export([new/1, do_clone/0, to_string/0]). -constructor([new/1]). 5 6 attributes. 7 8 Name. 9 10 methods. 11 12 13 new(Name) -> self::Name = Name. 14 15 16 do_clone() -> person::new(self::Name). 17 18 19 20 to_string() -> String = "This person is named " ++ self::Name, String. Código 3.5.2: Classe Person em ooErlang Pode ser verificado nas implementações mostradas em 3.5.1 e 3.5.2 a semelhança entre as sintaxes das referidas linguagens, sendo que cada uma utiliza suas caracterı́sticas Padrão Prototype 51 próprias. A interface Prototype possui o método doClone(), referente ao método que trata de utilizar objetos já existentes como protótipos para criação de novos objetos de forma menos complexa. Os códigos 3.5.3 e 3.5.4 mostram, respectivamente, as implementações em Java e ooErlang da interface Prototype. 1 public interface Prototype { 2 public Prototype doClone(); 3 4 5 } Código 3.5.3: Interface Prototype em Java 1 2 -interface(prototype). -export([do_clone/0]). 3 4 methods. 5 6 do_clone(). Código 3.5.4: Interface Prototype em ooErlang Percebe-se então que sempre que um objeto precise ser criado, e que o mesmo objeto possui caracterı́sticas de outro que já foi anteriormente instanciado, o objeto já instanciado pode ser utilizado como um protótipo para a criação do novo objeto, fazendo com que o novo objeto seja construı́do já possuindo as caracterı́sticas principais do protótipo em questão. Dessa forma não será necessário ter todo o trabalho de “configurar” um novo objeto, dependendo da situação. A utilização de protótipos facilita o trabalho de reutilização de objetos na criação de novos objetos. Na execução deste exemplo, são instanciados dois objetos, um para cada classe (Dog e Person). Em seguida, para cada um destes novos objetos criados é criado um outro objeto, utilizando-se destes primeiros como protótipos, ou seja, os novos objetos criados passam a ter as caracterı́sticas principais dos objetos já criados. Isto é possı́vel devido à utilização do método responsável por clonar objetos utilizando outros objetos como protótipos na classe Prototype. As figuras 3.11 e 3.12 mostram a execução deste exemplo em Java e ooErlang. Verifica-se, observando a execução destes exemplos, que tanto em Java quanto em ooErlang os resultados foram os mesmos. Os objetos inicialmente criados possuem cada Padrão Prototype 52 Figura 3.11: Execução do exemplo para o padrão Prototype em Java. um suas caracterı́sticas próprias, e os objetos originados com base nos objetos já existentes, mostraram um comportamento semelhante ao apresentado por seus protótipos. A extensão do Erlang, ooErlang mostrou-se apta a implementar este exemplo que possui a sı́ntese de utilização do padrão Prototype, em uma aplicação simples e eficiente. Verificase que, assim como o Java, o ooErlang pode ser utilizado para dar suporte a sistemas orientados a objetos com a facilidade de utilização deste padrão de projetos, por possuir as caracterı́sticas principais do paradigma orientado a objetos. Algumas caracterı́sticas podem ser notadas na utilização deste padrão de projetos. Dependendo da aplicação na qual o mesmo seja utilizado, é possı́vel adicionar e remover novos objetos em tempo de execução, assim como é viável especificar novos objetos apenas variando seus valores e caracterı́sticas. Na utilização deste padrão também pode-se reduzir a quantidade de subclasses, paralelizando a hierarquia de classes de objetos assim Padrão Singleton 53 Figura 3.12: Execução do exemplo para o padrão Prototype em ooErlang. como configurar uma aplicação com classes de forma dinâmica. Estas caracterı́sticas foram documentadas em [Gamma et al., 1994], no padrão Prototype. 3.6 Padrão Singleton A definição deste padrão pode ser escrita da seguinte forma: “Garantir que uma classe só tenha uma única instância, e prover um ponto de acesso global a ela” [Gamma et al., 1994]. Desta forma pode-se verificar que o padrão de projetos Singleton é necessário e aplicável em situações onde seja importante possuir apenas uma instância de determinada classe, evitando assim erros de inconsistência de dados, dependendo do sistema que estiver sendo modelado. Pode-se então afirmar que o padrão Singleton deve ser aplicado a sistemas na situação em que precise haver exatamente uma única instância de uma classe, sendo que esta instância deve ser acessı́vel para os usuários por meio de um ponto de acesso bem conhecido. Outra situação em que este padrão pode ser utilizado é quando a única instância precisa ser estendida por subclasses, permitindo aos usuários utilizar a instância estendida sem Padrão Singleton 54 que seja necessário modificar seu código. 3.6.1 Exemplo utilizado para este padrão No exemplo utilizado para aplicação deste padrão, retirado de [Freeman et al., 2004], foi modelado um sistema para gerenciamento de uma fábrica que produz chocolates. A caracterı́stica principal desta fábrica é a de que existe uma e apenas uma caldeira para realizar o derretimento e o preparo do chocolate a ser produzido. Isto significa dizer que deve-se considerar as ações de fabricação em relação à caldeira, tendo a mesma como referência. Para se inserir matéria prima nesta caldeira, é necessário verificar se a mesma está vazia. Para se retirar chocolate derretido desta caldeira, deve-se primeiramente verificar se esta caldeira possui chocolate. Ou seja, para a modelagem a ser utilizada, é necessário que exista apenas uma única instância da caldeira desta fábrica, sendo que a mesma deve possuir um único ponto de acesso global, evitando problemas de fabricação. A figura 3.13 mostra, para melhor entendimento, um diagrama de classes desta modelagem. Figura 3.13: Diagrama de classes do exemplo para o padrão Singleton. Observa-se que esta modelagem possui apenas duas classes, a classe ChocolateControl- Padrão Singleton 55 ler, que possui o método principal, e de onde o exemplo é executado, e a classe ChocolateBoiler, responsável por possuir em um de seus atributos o atributo uniqueInstance. Esta é a única instância referente à caldeira da fábrica de chocolates explicada anteriormente. Os métodos da classe ChocolateBoiler permitem ao cliente encher, esvaziar e acender a fornalha da caldeira (fill(), drain() e boil() respectivamente), além outros dois métodos responsáveis por verificar o estado atual da caldeira, caso a mesma esteja vazia ou cheia (isEmpty() e isBoiled()). Desta forma, quando se vai encher a caldeira, deve-se verificar se a mesma já não está cheia, e vice-versa. Verifica-se então a necessidade de haver apenas uma única instância para a caldeira, assim como apenas um ponto de acesso global a este objeto. Desta forma, se dois objetos tentarem acessar a caldeira ao mesmo tempo, não vão haver problemas de inconsistências e de erros de execução. É necessário portanto que haja um método especı́fico para que se obtenha o ponto de acesso global a esta caldeira, e este método está implementado na classe ChocolateBoiler, e chama-se getInstance(). 3.6.2 Comparação das implementações em Java e ooErlang As classes ChocolateController e ChocolateBoiler são implementadas em Java e ooErlang de acordo com suas caracterı́sticas próprias. Em Java, na classe ChocolateBoiler, o método responsável por retornar o único ponto de acesso é implementado utilizando-se de um atributo estático, e dessa forma, sempre que seja necessário acessar este objeto, é verificado se o atributo já foi instanciado, criando-o em caso negativo e retornando-o em caso positivo. O código 3.6.1 mostra essa implementação em Java. Em ooErlang esta implementação também foi realizada, porém com algumas diferenças. Enquanto que em Java é possivel utilizar-se de atributos estáticos, em ooErlang isto não é possı́vel. A solução seguida foi a de se utilizar processos registrados. Todo objeto em ooErlang é um processo. A instância única também o é. Dessa forma o método para retornar o ponto de acesso verifica se o atributo da instância única já foi registrado ou não, sendo criado e registrado em caso negativo e apenas retornado em caso positivo. O código 3.6.2 mostra esta implementação em ooErlang. Em relação à classe ChocolateController, que foi, igualmente, implementada em Java e Padrão Singleton 1 2 3 4 56 public class ChocolateBoiler { private boolean empty; private boolean boiled; private static ChocolateBoiler uniqueInstance; 5 private ChocolateBoiler() { empty = true; boiled = false; } 6 7 8 9 10 public static ChocolateBoiler getInstance() { if (uniqueInstance == null) { System.out.println("Creating unique instance of Chocolate Boiler"); uniqueInstance = new ChocolateBoiler(); } System.out.println("Returning instance of Chocolate Boiler"); return uniqueInstance; } 11 12 13 14 15 16 17 18 19 Código 3.6.1: Classe ChocolateBoiler em Java ooErlang, esta é a classe principal, e seu objetivo é o de testar a implementação verificando a execução do fonte, e se cria apenas uma única instância com um ponto de acesso global a ela. Então é utilizado o método getInstance(), são realizadas algumas operações na caldeira e em seguida um outro objeto tenta ter acesso à instância, verificando se receberá a mesma instância criada anteriormente. Os códigos em 3.6.3 e 3.6.4 referem-se a esta classe em Java e ooErlang, respectivamente. Ao executar o método main() de ambas as implementações, verifica-se que seus resultados mostram inicialmente a instanciação da instância única relativa à caldeira da fábrica de chocolates, utilização de alguns métodos e, em seguida novamente a utilização do método getInstance(), verificando se, em ambos os casos, a mesma instância criada anteriormente é utilizada. As figuras 3.14 e 3.15 mostram o resultado da execução deste exemplo em Java e ooErlang. Observa-se, na implementação do exemplo referente ao padrão Singleton em ooErlang, uma diferença em relação à implementação em Java. Enquanto que em Java é possı́vel criar variáveis estáticas, em ooErlang isto não é utilizável, portanto é necessário utilizar outra ferramenta bastante conhecida em Erlang que, por consequência, também está presente em ooErlang. Trata-se da utilização de processos registrados. Especificamente no exemplo do padrão Singleton em ooErlang, é necessário utilizar- Padrão Singleton 1 2 3 4 57 -class(chocolateBoiler). -export([new/0, get_instance/0, fill/0, drain/0, boil/0]). -export([is_empty/0, is_boiled/0]). -constructor([new/0]). 5 6 attributes. 7 8 9 10 Empty; Boiled; UniqueInstance. 11 12 methods. 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 get_instance() -> ListOfProcesses = erlang:registered(), Return = lists:member(unique_instance, ListOfProcesses), if (Return == false) -> io:format("Creating unique instance of Chocolate Boiler ~n"), self::UniqueInstance = chocolateBoiler::new(), erlang:register(unique_instance, ObjectID); true -> io:format("") end, io:format("Returning instance of Chocolate Boiler ~n"), Unique = {chocolateBoiler, whereis(unique_instance)}, Unique::UniqueInstance. Código 3.6.2: Classe ChocolateBoiler em ooErlang 1 2 3 4 5 6 public class ChocolateController { public static void main(String args[]) { ChocolateBoiler boiler = ChocolateBoiler.getInstance(); boiler.fill(); boiler.boil(); boiler.drain(); 7 // will return the existing instance ChocolateBoiler boiler2 = ChocolateBoiler.getInstance(); 8 9 } 10 11 } Código 3.6.3: Classe principal ChocolateController em Java se de processos registrados na criação da instância relativa à caldeira. Desta forma é possı́vel obter um ponto de acesso global único a esta instância. Na primeira instanciação, é realizado o registro do referente processo. Nas outras tentativas de obter a instância única, verifica-se que este processo já foi registrado, retornando-se apenas a instância única Padrão Singleton 1 2 58 -class(chocolateController). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 10 11 main() -> Boiler = chocolateBoiler::new(), Boiler::get_instance(), Boiler::fill(), Boiler::boil(), Boiler::drain(), 12 13 14 Boiler2 = chocolateBoiler::new(), Boiler2::get_instance(). Código 3.6.4: Classe principal ChocolateController em ooErlang Figura 3.14: Execução do exemplo para o padrão Singleton em Java. necessária. Assim consegue-se utilizar o padrão Singleton em ooErlang. Algumas consequências na utilização deste padrão podem ser verificadas, como por Conclusões dos Padrões de Projeto de Criação em ooErlang 59 Figura 3.15: Execução do exemplo para o padrão Singleton em ooErlang. exemplo, acesso controlado a uma única instância, facilidade de refinamento de operações quando a classe única for herdada por subclasses, permissão na utilização de um número variado de instâncias dependendo das caracterı́sticas próprias do sistema. 3.7 Conclusões dos Padrões de Projeto de Criação em ooErlang Os padrões de projeto de criação, conforme demonstrado pelos exemplos utilizados, de uma forma geral, possuem a caracterı́stica principal de abstrair o processo de instanciação. Duas principais abordagens podem ser observadas na utilização dos padrões de criação. Em uma delas o encapsulamento está relacionado com as classes concretas que um sistema realmente utiliza. A outra abordagem tem por objetivo encapsular as instâncias destas classes concretas e as relações entre as mesmas. Nas implementações dos exemplos utilizados para os padrões de criação, muitas das caracterı́sticas de implementação utilizadas nos fontes em Java são, semelhantemente, uti- Conclusões dos Padrões de Projeto de Criação em ooErlang 60 lizadas em ooErlang. É importante notar, entretanto, que no exemplo do padrão de projetos Singleton, é utilizada uma caracterı́stica própria do Erlang também presente no ooErlang, a de nomeação de processos. Uma vez que no ooErlang não é possivel criar variáveis estáticas, as mesmas podem ser substituı́das por processos registrados e nomeados. Todos os cinco (5) padrões de criação implementados em ooErlang de acordo com os fontes usados como referência em Java apresentam comportamento similar quando executado após terem sido compilados por meio do compilador do ooErlang. A quantidade de classes implementadas tanto em Java quanto em ooErlang é a mesma, ou seja, ao se utilizar do ooErlang, não é necessário criar uma quantidade diferente de classes presentes nas implementações em Java. Capı́tulo 4 Padrões de Projeto Estruturais No presente capı́tulo são analisados os sete (7) padrões de projetos do tipo estrutural. Estes padrões foram inicialmente definidos por [Gamma et al., 1994], sendo estudados em suas caracterı́sticas próprias e em suas diferentes formas de aplicação, com a ajuda de exemplos práticos para cada um dos padrões estruturais abordados. Alguns códigos-fontes são apresentados para ilustrar melhor os exemplos, assim como para comparar a sintaxe do Java com a extensão ooErlang, verificando os resultados obtidos nas execuções dos exemplos utilizados. 4.1 Conceitos Iniciais Padrões Estruturais se preocupam em como as classes e objetos são compostos para formar largas estruturas [Gamma et al., 1994]. Todos os padrões de projetos que fazem parte desta classificação possuem alguma caracterı́stica própria bem definida, relacionada com as estruturas de relação entre as classes pertencentes à modelagem de determinado sistema. Seu foco é na solução de algum problema estrutural, e também relacional entre as referidas classes e interfaces do modelo em questão. Uma vantagem que pode ser verificada na utilização dos padrões de projetos estruturais é, por exemplo, a possibilidade de resolver problemas que envolvam duas bibliotecas de classes que foram desenvolvidas inicialmente de forma isolada, fazendo com que possam Padrão Adapter 62 trabalhar em conjunto. Outra vantagem é quando deve-se adaptar a interface de uma classe para outra classe que, a priori, não pertence ao projeto original. Esta abordagem é bastante utilizada, sendo fácil encontrar situações nas quais os padrões estruturais podem ser aplicados. Os padrões de projetos estruturais, apesar de serem diferentes entre si, no que diz respeito ao problema que visam resolver, não são mutuamente exclusivos. Isto significa afirmar que podem existir situações em que seja recomendado, ou até mesmo necessário utilizar mais de um padrão de projeto simultaneamente. Em muitos casos estes padrões se complementam, dependendo do sistema a ser modelado. Muitos padrões de projetos estruturais estão relacionados em algum grau [Gamma et al., 1994]. Todos os códigos-fonte deste capı́tulo podem ser encontrados em [GitHub, 2008]. 4.2 Padrão Adapter Verificando-se sua definição inicial observa-se que o padrão Adapter tem por objetivo: “Converter a interface de uma classe em outra interface esperada pelos clientes. Adapter permite a comunicação entre classes que não poderiam trabalhar juntas devido à incompatibilidade de suas interfaces” [Gamma et al., 1994]. Esta aplicação é geralmente utilizada em situações onde um sistema ou parte dele é utilizado em outro sistema. Observa-se, desta forma, que este padrão de projetos pode ser bem aplicado quando seja necessário utilizar uma classe já existente, e a interface desenvolvida não é compatı́vel com a referida classe. Outra forma de se aplicar este padrão pode ser observada quando seja preciso criar uma classe reutilizável, que se relaciona com classes distintas, sendo que estas classes não possuem necessariamente interfaces compatı́veis, sendo necessário adaptar estas interfaces. 4.2.1 Exemplo utilizado para este padrão Para demonstrar a utilização deste padrão, é utilizado um exemplo retirado de [Freeman et al., 2004], no qual são implementadas interfaces referentes a dois animais distintos: Patos (Duck) e Perus (Turkey). Estas interfaces possuem, cada uma, dois métodos. Para os patos, Padrão Adapter 63 os métodos são: quack() (referente ao som do pato) e fly() (vôo do pato). Já no caso dos perus, temos os métodos gobble() (som do objeto peru) e fly() (semelhante ao método de vôo do objeto pato). Percebe-se que as interfaces não são compatı́veis. O objetivo aqui é fazer com que um objeto do tipo pato possa ser manipulado por meio de uma interface da classe peru, e vice-versa. Se formos simplesmente tentar utilizar uma interface de “pato” para um objeto “peru”, haverá incompatibilidade de métodos, retornando erro na saı́da. A solução utilizada é a de criar duas classes do tipo “Adapter”. Uma das classes será responsável por adaptar a interface da classe pato (Duck) com os objetos criados da classe peru (Turkey). Essa classe possui o nome TurkeyAdapter pois adapta o objeto da classe Turkey com a interface da classe Duck. Consequentemente, a classe que adapta os objetos da classe Duck com a interface da classe Turkey possui o nome DuckAdapter. A figura 4.1 mostra um diagrama de classes para melhor entendimento deste cenário. Figura 4.1: Diagrama de classes do exemplo para o padrão Adapter. Além das interfaces Duck e Turkey, são definidas duas classes, uma relacionada com os patos, no caso MallardDuck, e outra relacionada com os perus, WildTurkey. As duas Padrão Adapter 64 classes que funcionam como adaptadores, são DuckAdapter e TurkeyAdapter, conforme explicado anteriormente. Para complementar este exemplo, são criadas duas classes com o método principal (main()), sendo elas DuckTestDrive e TurkeyTestDrive. Ambas testam os adaptadores, verificando se obtiveram os resultados esperados. 4.2.2 Comparação das implementações em Java e ooErlang Em Java e ooErlang todas as classes e interfaces implementadas são similares. As interfaces Duck e Turkey são também similares, sendo que o diferencial entre elas é, principalmente, o método relacionado ao som do animal. Entretanto ambas possuem implementações semelhantes. Os códigos mostrados em 4.2.1 e 4.2.2 mostram as implementações em Java e ooErlang, respectivamente, destas interfaces. 1 2 3 4 public interface Duck { public void quack(); public void fly(); } Código 4.2.1: Interface Duck em Java 1 2 -interface(duck). -export([quack/0, fly/0]). 3 4 methods. 5 6 7 quack(). fly(). Código 4.2.2: Interface Duck em ooErlang A diferença entre a interface Duck e a interface Turkey é que, em Turkey, ao invés de existir o método quack(), existe o método gobble(). Esta é a incompatibilidade destas interfaces. A classe TurkeyAdapter implementa o adaptador que permite utilizar a interface Duck para uma instância qualquer da classe WildTurkey, de acordo com o objetivo principal do padrão Adapter. Os códigos mostrados em 4.2.3 e 4.2.4 mostram a implementação do adaptador TurkeyAdapter em Java e ooErlang, nesta ordem. Padrão Adapter 1 2 65 public class TurkeyAdapter implements Duck { Turkey turkey; 3 public TurkeyAdapter(Turkey turkey) { this.turkey = turkey; } 4 5 6 7 public void quack() { turkey.gobble(); } 8 9 10 11 public void fly() { for(int i=0; i < 5; i++) { turkey.fly(); } } 12 13 14 15 16 17 } Código 4.2.3: Classe TurkeyAdapter em Java 1 2 3 4 -class(turkeyAdapter). -implements(duck). -export([new/1, quack/0, fly/0]). -constructor([new/1]). 5 6 attributes. 7 8 Turkey. 9 10 methods. 11 12 13 new(Turkey) -> self::Turkey = Turkey. 14 15 16 17 quack() -> Temp = self::Turkey, Temp::gobble(). 18 19 20 21 22 23 24 25 fly() -> Temp = self::Turkey, Temp::fly(), Temp::fly(), Temp::fly(), Temp::fly(), Temp::fly(). Código 4.2.4: Classe TurkeyAdapter em ooErlang Conforme pode ser observado, houveram adaptações para os dois métodos da classe WildTurkey, gobble() e fly(). Desta forma, o usuário ao fazer uso do adaptador TurkeyAdapter, poderá chamar os métodos da interface Duck para um objeto da classe WildTurkey, Padrão Adapter 66 e observar que os resultados demonstram uma adaptação entre uma interface que inicialmente era incompatı́vel, mas que agora já pode ser utilizada para outro tipo de objeto, no caso, WildTurkey. As classes DuckTestDrive e TurkeyTestDrive tem como objetivo verificar o funcionamento dos adaptadores de interface para um objeto da classe WildTurkey utilizar a interface TurkeyAdapter e para um objeto da classe MallardDuck utilizar a interface DuckAdapter. Dessa forma, cada uma das classes acima citadas instancia um adaptador que deseja testar para ser possivel verificar os resultados obtidos. Os códigos 4.2.5 e 4.2.6 mostram a implementação da classe TurkeyTestDrive em Java e ooErlang respectivamente. 1 2 3 4 public class TurkeyTestDrive { public static void main(String[] args) { MallardDuck duck = new MallardDuck(); Turkey duckAdapter = new DuckAdapter(duck); 5 for(int i=0;i<10;i++) { System.out.println("The DuckAdapter says..."); duckAdapter.gobble(); duckAdapter.fly(); } 6 7 8 9 10 } 11 12 } Código 4.2.5: Classe principal TurkeyTestDrive em Java 1 2 -class(turkeyTestDrive). -export([main/0]). 3 4 class_methods. 5 6 7 8 main() -> Duck = mallardDuck::new_(), DuckAdapter = duckAdapter::new(Duck), 9 10 11 12 13 14 15 16 17 18 io:format("The DuckAdapter says..."), DuckAdapter::gobble(), DuckAdapter::fly(), io:format("The DuckAdapter says..."), DuckAdapter::gobble(), DuckAdapter::fly(), io:format("The DuckAdapter says..."), DuckAdapter::gobble(), DuckAdapter::fly(). Código 4.2.6: Classe principal TurkeyTestDrive em ooErlang Padrão Adapter 67 Desta forma, na implementação mostrada, inicialmente é instanciado um objeto do tipo “pato”, pertencente à classe MallardDuck. Em seguida este objeto é utilizado para que se possa instanciar uma classe que irá funcionar como adaptador, DuckAdapter, para verificar se o objeto originalmente do tipo “pato” pode ser utilizado com métodos da interface para o objeto “peru”. Dessa forma utilizam-se os dois métodos da interface Turkey, comprovando a compatibilidade gerada. As figuras 4.2 e 4.3 mostram o resultado da execução deste exemplo em Java e ooErlang, respectivamente. Figura 4.2: Execução do exemplo para o padrão Adapter em Java. Este padrão é comumente utilizado em muitos casos reais, devido à facilidade e necessidade de aplicação do mesmo em diferentes situações. A implementação em ooErlang mostrou-se semelhante à utilizada em Java, e seus resultados demonstraram comportamento similar para ambas as linguagens de programação. Este resultado demonstra a flexibilidade na programação utilizando a extensão ooErlang. Padrão Bridge 68 Figura 4.3: Execução do exemplo para o padrão Adapter em ooErlang. Algumas considerações devem ser feitas na utilização do padrão de projetos Adapter. Por exemplo, o nı́vel de adaptação que deve ser implementado, em relação à classe adaptada a sua relativa interface. Isto varia muito, havendo casos em que a adaptação deve ser total e, em outros casos, apenas parcial. Esta decisão tem relação estreita com o tipo de interface para a qual se irá implementar o adaptador. Caso esta interface não possua todos os métodos presentes na aplicação, é bem provável que haja uma adaptação parcial na implementação. 4.3 Padrão Bridge De acordo com sua definição, o padrão Bridge tem por finalidade: “Desacoplar uma abstração de sua implementação para que os dois possam variar independentemente” [Gamma et al., 1994]. Em situações nas quais podem existir vários tipos diferentes de abstrações, assim como diversas implementações, a adoção e aplicação deste padrão facilita a manutenção do código-fonte, assim como a extensão, pois facilita o acréscimo de novas abstrações, sem impactar o projeto como um todo. Padrão Bridge 69 Quando se necessite, por exemplo, selecionar ou modificar uma implementação em tempo de execução, deve ser necessário evitar uma ligação forte entre esta implementação e sua referente abstração. Neste caso a utilização do padrão Bridge torna-se necessária. Também pode ser verificado que, ao separar uma abstração de sua implementação, qualquer mudança que se faça na implementação não irá afetar sua respectiva abstração, facilitando assim a manutenção do fonte. 4.3.1 Exemplo utilizado para este padrão Para exemplificar a utilização do padrão de projetos Bridge, é utilizado um exemplo retirado de [Kulandai, 2012], no qual é modelada uma fábrica que produz dois tipos principais de meios de transportes: carros e bicicletas. Além destes dois produtos, existem duas formas com as quais é possı́vel fabricá-los: produzindo por meio de maquinário fabril e montando de forma manual. Ou seja, existem dois tipos diferentes de produtos e cada produto possui duas formas diferentes de fabricação. A classe que representa o produto “carro” possui o nome Car, e a classe que representa o produto “bicicleta” está designada como Bike. A fabricação por meio de processos automatizados da fábrica possui uma classe com o nome Produce, e a fabricação manual tem sua classe, chamada Assemble. Além destas classes, o cenário possui uma classe geral dos veı́culos, chamada Vehicle, que é herdada por ambos os produtos fabricados. A figura 4.4 mostra um diagrama de classes para melhor entendimento do cenário. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Como pode ser observado, o padrão Bridge já está sendo utilizado. De acordo com este padrão , o objetivo principal é o de separar uma abstração de sua implementação. Neste caso especı́fico, as abstrações são os tipos de produtos e as implementações são as formas com as quais cada um desses produtos são fabricados. Existe um desacoplamento entre os produtos e seus tipos de fabricação. Caso este padrão não estivesse sendo utilizado, uma solução que poderia vir a ser usada seria a de criar a classe Vehicle sendo herdada pelos dois tipos de veı́culos. Em seguida, cada veı́culo estaria associado com ambos os métodos de fabricação. Desta forma haveria duplicação de códigos, pois para cada veı́culo haveriam duas classes Produce e duas classes Padrão Bridge 70 Figura 4.4: Diagrama de classes do exemplo para o padrão Bridge. Assemble. As classes estariam fortemente acopladas e se fosse necessário incluir um novo tipo de produto, haveria grande impacto e retrabalho no projeto. Porém, neste caso, foi implementado um desacoplamento entre os tipos de veı́culos e suas formas de fabricação. Por meio da interface Workshop, referente às formas de fabricação, a duplicação de códigos foi evitada e se for necessário inserir novos veı́culos ou novas formas de fabricação, isto não acarretará grandes problemas ao projeto como um todo. 4.3.2 Comparação das implementações em Java e ooErlang As classes mostradas no diagrama referente à figura 4.4 foram todas implementadas em ooErlang, tendo-se em vista que a implementação em Java foi retirada de [Kulandai, 2012]. Observadas e ressalvadas as relativas peculiaridades na sintaxe de cada uma das linguagens, as implementações em ooErlang apresentam-se semelhantes às mostradas em Java. Os códigos observados em 4.3.1 e 4.3.2 revelam a implementação da classe Bike em Java e ooErlang, respectivamente. As implementações da classe Bike seguem a mesma linha de raciocı́nio em relação às Padrão Bridge 1 71 public class Bike extends Vehicle { 2 public Bike(Workshop workShop1, Workshop workShop2) { super(workShop1, workShop2); } 3 4 5 6 public void manufacture() { System.out.print("Bike "); workShop1.work(); workShop2.work(); } 7 8 9 10 11 12 13 } Código 4.3.1: Classe Bike em Java 1 2 3 4 -class(bike). -extends(vehicle). -export([new/2, manufacture/0]). -constructor([new/2]). 5 6 methods. 7 8 9 10 new(WorkShop1, WorkShop2) -> self::WorkShop1 = WorkShop1, self::WorkShop2 = WorkShop2. 11 12 13 14 15 16 17 manufacture() -> io:format("Bike "), Work1 = self::WorkShop1, Work2 = self::WorkShop2, Work1::work(), Work2::work(). Código 4.3.2: Classe Bike em ooErlang implementações da classe Car, por isso apenas o fonte da classe Bike é mostrado. Deve-se observar que cada objeto da classe Bike recebe como parâmetro duas formas de fabricação, referente às duas maneiras com que pode ser produzido. Assim como a classe Car, a classe Bike herda os atributos e métodos da classe Vehicle. Analisando os códigos da classe Assemble, mostrados em 4.3.3 e 4.3.4 (Java e ooErlang respectivamente), pode-se notar que esta classe implementa o método work(), presente na interface Workshop. Isto ocorre de forma semelhante com o fonte da classe Produce. A utilização da interface Workshop é o ponto fundamental de aplicação do padrão Bridge neste exemplo, possibilitando separar os objetos de suas relativas formas de fabricação. Padrão Bridge 1 72 public class Assemble implements Workshop { 2 public void work() { System.out.println(" Assembled."); } 3 4 5 6 } Código 4.3.3: Classe Assemble em Java 1 2 3 -class(assemble). -implements(workshop). -export([work/0]). 4 5 methods. 6 7 8 work() -> io:format(" Assembled. ~n"). Código 4.3.4: Classe Assemble em ooErlang A classe Vehicle possui dois atributos, relacionados com os dois tipos de fabricação dos veı́culos da fábrica utilizada no exemplo. Os métodos desta classe são um construtor, que apenas instancia um objeto, dependendo da classe principal (BridgePattern) e o método abstrato manufacture(), que é implementado nas classes Bike e Car. Em relação à classe principal, BridgePattern, para testar a utilização do padrão Bridge, são instanciados dois objetos, um do tipo Car e outro do tipo Bike. Para cada um destes, é passado como parâmetro de construção os dois distintos modos de fabricação de um veı́culo. Em seguida é utilizado o método manufacture(), que é implementado em cada um dos veı́culos, e realiza a fabricação do veı́culo das duas formas existentes. A implementação desta classe está mostrada nos códigos em 4.3.5 e 4.3.6. 1 public class BridgePattern { 2 public static void main(String[] args) { 3 4 Vehicle vehicle1 = new Car(new Produce(), new Assemble()); vehicle1.manufacture(); Vehicle vehicle2 = new Bike(new Produce(), new Assemble()); vehicle2.manufacture(); 5 6 7 8 } 9 10 } Código 4.3.5: Classe principal BridgePattern em Java Padrão Bridge 1 2 73 -class(bridgePattern). -export([main/0]). 3 4 class_methods. 5 6 7 8 main() -> Vehicle1 = car::new(produce::new_(), assemble::new_()), Vehicle1::manufacture(), 9 10 11 Vehicle2 = bike::new(produce::new_(), assemble::new_()), Vehicle2::manufacture(). Código 4.3.6: Classe principal BridgePattern em ooErlang Assim para ambos os veı́culos, os dois métodos de fabricação são testados. Percebe-se assim que, dependendo da necessidade do sistema, poderiam ser incluı́dos novos veı́culos e eles já poderiam possuir as duas formas de fabricação que já existem, sem necessidade de alterar nenhum código em qualquer outra classe de veı́culos. O mesmo é valido em uma situação em que fosse necessário retirar de fabricação algum tipo de veı́culo. As figuras 4.5 e 4.6 mostram a execução deste exemplo em Java e ooErlang. A extensão do Erlang para orientação à objetos, ooErlang, mostrou poder ser utilizada na implementação de fontes similares aos fontes utilizados no exemplo mostrado para o padrão Bridge. Este padrão estrutural é bem aplicado em situações onde seja necessário variar as implementações distintamente de suas abstrações. Com a aplicação deste padrão de projeto, é possı́vel perceber que a inserção e/ou exclusão de novos veı́culos na fábrica utilizada neste exemplo, não iria ocasionar um grande impacto no projeto como um todo. Similarmente, se fosse necessário incluir novas formas de fabricação de veı́culos, ou retirar alguma forma já existente, também não haveriam impactos no projeto inteiro. As principais consequencias provindas da utilização deste padrão de projetos podem ser citadas como sendo o desacoplamento de uma interface com uma implementação (eliminando dependências), melhoramento da extensibilidade do fonte em relação às hierarquias de abstração e implementação (tornam-se independentes), e a caracterı́stica de encapsulamento deste padrão, que não mostra ao usuário detalhes da implementação que não são do interesse do mesmo. Padrão Composite 74 Figura 4.5: Execução do exemplo para o padrão Bridge em Java. 4.4 Padrão Composite De acordo com sua definição, o padrão de projetos Composite tem por objetivo: “Compor objetos em estruturas de árvore para representar hierarquias todo-parte. Composite permite que clientes tratem objetos individuais e composições de objetos de maneira uniforme” [Gamma et al., 1994]. Assim, sempre que um cenário tenha como caracterı́stica principal a necessidade de tratar diferentes estruturas de objetos em hierarquias, tendo acesso a todos os nós da árvore, é possı́vel utilizar o padrão Composite. A necessidade de utilização deste padrão é verificada quando, ao se possuir vários objetos configurados em estruturas semelhantes ou diferentes, seja necessário representar hierarquias todo-parte destes objetos e suas referidas estruturas. Também pode-se dizer que este padrão é aplicável quando o objetivo da modelagem é o de permitir ao usuário do sistema ser capaz de ignorar a diferença entre composições de objetos e objetos individuais, Padrão Composite 75 Figura 4.6: Execução do exemplo para o padrão Bridge em ooErlang. tratando-os uniformemente. 4.4.1 Exemplo utilizado para este padrão Para tratar do padrão de projetos Composite, é utilizado um exemplo retirado de [Making, 2013], no qual trabalha-se com uma estrutura de hierarquias. É uma estrutura simplificada, cujo objetivo restringe-se apenas a verificar a utilização do referido padrão e a forma com que pode-se aplicar este padrão a diferentes sistemas que possuam o mesmo tipo de problema e que necessitem de uma solução semelhante. Na estrutura utilizada existe uma classe chamada Primitive. Trata-se de um objeto isolado, que não esteja contido em uma composição. Este objeto armazena um valor numérico. Também tem-se uma classe chamada Composite. Esta classe é uma composição de objetos da classe Primitive. A classe Composite é herdada por outras duas classes, Row (linha) e Column (coluna). Cada linha é uma composição de valores Primitive, e o mesmo é válido para as colunas (Column). As classes Row e Column são tipos diferentes de composições. Podem armazenar diversos valores, sejam eles de outras composições (Composite) ou valores isolados (Primitive). Padrão Composite 76 Eis a caracterı́stica do padrão Composite neste exemplo: é possı́vel criar uma estrutura do tipo todo-parte, e deve-se implementar uma forma de se trabalhar com todas as hierarquias de classes de maneira uniforme. Ambas as classes Composite e Primitive implementam a interface Component. Isto significa dizer que será criada uma estrutura com linhas e colunas e que, tanto as composições de objetos quanto os objetos isolados são componentes pertencentes a essa mesma estrutura. A figura 4.7 mostra com mais detalhes um diagrama de classes referente a este exemplo para melhor entendimento do problema. Figura 4.7: Diagrama de classes do exemplo para o padrão Composite. A classe Composite possui um atributo chamado “children”, que é uma lista na qual é possivel armazenar outros objetos de composição, sejam linhas ou colunas, assim como também é possivel armazenar valores isolados. Considerando que podem-se armazenar várias composições de objetos dentro de uma composição e em cada uma destas composições também armazena outras composições, percebe-se como uma simples estrutura pode tornar-se bastante complexa com diversas inserções ao longo do tempo. Padrão Composite 4.4.2 77 Comparação das implementações em Java e ooErlang Todas as classes mostradas na figura 4.7 são implementadas em Java e ooErlang. Analisando inicialmente a classe Composite, verifica-se, de acordo com o código mostrado em 4.4.1 (implementação em Java desta classe), que esta classe possui como atributo um valor e uma lista em que podem ser armazenados outros valores. Além do construtor, existe um método responsável por inserir elementos na lista “filhos” e um método chamado transverse(). 1 2 3 4 public class Composite implements Component{ private Component[] children = new Component[9]; private int total; private int value; 5 public Composite(int val) { value = val; total = 0; } 6 7 8 9 10 public void add(Component c) { children[total++] = c; } 11 12 13 14 public void transverse() { System.out.print( value + " " ); for (int i=0; i < total; i++) children[i].transverse(); 15 16 17 18 } 19 20 } Código 4.4.1: Classe Composite em Java O método transverse() é o responsável por fazer a varredura na estrutura, conforme a definição do padrão. Este método imprime o valor da estrutura atual e é chamado recursivamente para todos os elementos filhos da estrutura atual. Desta forma, todos os elementos (sejam composições de objetos ou objetos isolados) que tiverem uma hierarquia menor que a atual serão visitados. O código em 4.4.2 mostra a implementação desta mesma classe em ooErlang. A classe Primitive é semelhante à classe Composite, porém a mesma não possui uma lista para elementos filhos da estrutura. Ambas herdam os métodos da classe Component. Além disso, a classe Composite, conforme dito anteriormente, é herdada por duas classes que representam tipos diferentes de composição: Row e Column (linha e coluna). Suas im- Padrão Composite 1 2 3 4 78 -class(composite). -implements(component). -export([new/1, add/1, transverse/0]). -constructor([new/1]). 5 6 attributes. 7 8 9 10 Children; Total; Value. 11 12 methods. 13 14 15 16 17 new(Val) -> self::Children = [], self::Value = Val, self::Total = 0. 18 19 20 21 add(C) -> self::Children = self::Children ++ [C], self::Total = self::Total + 1. 22 23 24 25 transverse() -> io:format("~p ", [self::Value]), transverse_aux(self::Children). 26 27 28 29 30 transverse_aux([]) -> ok; transverse_aux([Child|Children]) -> Child::transverse(), transverse_aux(Children). Código 4.4.2: Classe Composite em ooErlang plementações são bastante semelhantes. Os códigos mostrados em 4.4.3 e 4.4.4 apresentam as implementações da classe Row em Java e ooErlang respectivamente. 1 public class Row extends Composite{ 2 public Row(int val) { super( val ); } 3 4 5 6 public void transverse() { System.out.print( "Row" ); super.transverse(); } 7 8 9 10 11 } Código 4.4.3: Classe Row em Java Observa-se que esta classe possui um construtor e o método transverse(), responsável Padrão Composite 1 2 3 4 79 -class(row). -extends(composite). -export([new/1, transverse/0]). -constructor([new/1]). 5 6 methods. 7 8 9 10 11 new(Val) -> self::Children = [], self::Value = Val, self::Total = 0. 12 13 14 15 transverse() -> io:format("Row"), super::transverse(). Código 4.4.4: Classe Row em ooErlang por realizar a visitação em todos os elementos de hierarquia inferior ao atual. Ambos os métodos funcionam exatamente da mesma forma com a qual funcionam os mesmos métodos na superclasse Composite. Este comportamento é o mesmo encontrado na classe Column, que também herda os métodos da classe Composite. A classe principal, chamada CompositeDemo é a responsável por testar a criação da estrutura e verificação dos resultados na varredura da estrutura criada. Em sua implementação são instanciados três objetos do tipo linha (Row) e dois objetos do tipo coluna (Column). Em seguida estas composições são inseridas umas dentro das outras, assim como nesta estrutura também são inseridos valores isolados. Os códigos 4.4.5 e 4.4.6 mostram a implementação desta classe em Java e ooErlang. Tanto em Java quanto em ooErlang é criada a mesma estrutura, para facilitar a comparação da execução dos distintos fontes gerados. Após a criação das 3 linhas e das 2 colunas, as colunas 2 e 3 são inseridas na linha 1. Em seguida as linhas 4 e 5 são inseridas na linha 3. Depois destes passos são inseridos valores isolados na estrutura, em todos os objetos do tipo Composite criados. Desta forma logrou-se montar a estrutura de testes. Após toda a estrutura ter sido construı́da, é utilizado o método transverse(). O objeto que utilizou-se deste método é o mesmo que na estrutura formada está presente na maior hierarquia, no caso a primeira linha instanciada (Row). Desta forma garante-se que toda a estrutura será percorrida, uma vez que se sabe que o método transverse() funciona recursivamente para composições da estrutura de nı́vel inferior. As figuras 4.8 e 4.9 mostram Padrão Composite 1 80 public class CompositeDemo { 2 public static void main(String[] args) { Composite first = new Row( 1 ); Composite second = new Column( 2 ); Composite third = new Column( 3 ); Composite fourth = new Row( 4 ); Composite fifth = new Row( 5 ); first.add( second ); first.add( third ); third.add( fourth ); third.add( fifth ); first.add( new Primitive( 6 ) ); second.add( new Primitive( 7 ) ); third.add( new Primitive( 8 ) ); fourth.add( new Primitive( 9 ) ); fifth.add( new Primitive(10 ) ); first.transverse(); } 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // Row1 // | // +-- Col2 // | | // | +-// +-- Col3 // | | // | +-// | | // | | // | +-// | | // | | // | +-// +-- 6 7 Row4 | +-- 9 Row5 | +-- 10 8 } Código 4.4.5: Classe principal CompositeDemo em Java 1 2 -class(compositeDemo). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 10 11 main() -> First = row::new(1), Second = column::new(2), Third = column::new(3), Fourth = row::new(4), Fifth = row::new(5), 12 13 14 15 16 17 18 19 20 21 First::add(Second), First::add(Third), Third::add(Fourth), Third::add(Fifth), First::add(primitive::new(6)), Second::add(primitive::new(7)), Third::add(primitive::new(8)), Fourth::add(primitive::new(9)), Fifth::add(primitive::new(10)), %% Row1 %% | %% +-- Col2 %% | | %% | +-%% +-- Col3 %% | | %% | +-%% | | %% | | %% | +-%% | | %% | | %% | +-%% +-- 6 7 Row4 | +-- 9 Row5 | +-- 10 8 22 23 First::transverse(). Código 4.4.6: Classe principal CompositeDemo em ooErlang a execução deste exemplo em Java e ooErlang, respectivamente. Para melhor entendimento espacial da estrutura, nos fontes mostrados em 4.4.5 e 4.4.6, Padrão Composite 81 Figura 4.8: Execução do exemplo para o padrão Composite em Java. que representam as implementações em Java e ooErlang da classe CompositeDemo, inseriuse um comentário mostrando a estrutura gerada no final de toda a execução, sendo então percorrida por inteiro, conforme é verificado nas figuras 4.8 e 4.9. O padrão estrutural Composite mostra-se muito bem aplicado em situações de estruturas e composições de estruturas. Quando seja necessário trabalhar com diferentes estruturas de forma igual, a utilização deste padrão é necessária. Em ooErlang, assim como em Java, foi utilizada a recursão para que fosse possı́vel percorrer toda a estrutura formada. A recursão é uma solução prática e viável para este tipo de problema. Na utilização do padrão de projetos Composite, verifica-se a utilização de hierarquias de classes formada por objetos primitivos e objetos compostos. Percebe-se também que o usuário passa a tratar de maneira mais simples as composições e os objetos primitivos, uma vez que todos são tratados uniformemente. A inserção de novos tipos de componentes Padrão Decorator 82 Figura 4.9: Execução do exemplo para o padrão Composite em ooErlang. também é facilitada pela utilização deste padrão, evitando a necessidade de modificar o código-fonte de todo o projeto. 4.5 Padrão Decorator Este padrão de projeto, segundo sua formal definição, tem por objetivo: “Anexar responsabilidades adicionais a um objeto dinamicamente. Decorators oferecem uma alternativa flexı́vel ao uso de herança para estender uma funcionalidade” [Gamma et al., 1994]. Em certas situações, é mais conveniente incluir novas responsabilidades a um objeto sem que seja necessário modificar código fonte de suas subclasses, melhorando a utilização do referido objeto pelo usuário. Existe a possibilidade de se utilizar do padrão Decorator para uma melhor modelagem do sistema nos casos em que haja a necessidade de adicionar responsabilidades a objetos individuais de forma dinâmica e transparente, ou seja, sem que isto afete outros objetos. Outra situação em que este padrão pode ser utilizado é quando estender uma classe por subclasses mostre ser uma solução inadequada. Em alguns casos isto pode gerar excesso Padrão Decorator 83 de subclasses e consequente dificuldade na manutenção do sistema. 4.5.1 Exemplo utilizado para este padrão Para utilizar este padrão de projeto, foi utilizado um exemplo retirado de [Freeman et al., 2004], no qual é implementado um sistema gerenciador de uma loja de cafés. Na referida loja, é possı́vel escolher diferentes sabores de cafés, assim como é possı́vel, dependendo do cliente, colocar condimentos a mais no café escolhido. O sistema gerencia principalmente os pedidos dos clientes, verificando em seguida quanto foi o valor da compra. Existem quatro (4) sabores principais de café neste modelo: DarkRoast, Decaf, Expresso e HouseBlend. Cada sabor de café torna-se uma classe. Existem também quatro (4) tipos diferentes de condimentos, são eles: Milk, Mocha, Soy e Whip. Para cada pedido, o cliente escolhe o sabor do café e, se desejar, também adiciona quantos condimentos quiser. Ao final o valor do pedido é retornado ao cliente. A figura 4.10 mostra um diagrama de classes deste exemplo para melhor entendimento do exemplo. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Se a modelagem deste sistema tivesse escolhido resolver o problema criando várias subclasses, haveria a necessidade de criar inúmeras delas, uma vez que existem diversas combinações de diferentes sabores de cafés com diferentes tipos e quantidades de condimentos. Deve ser considerado o fato de que cada café possui um valor próprio e que cada condimento também possui um valor próprio, desta forma, os tipos de condimentos em um pedido de café incrementam o preço final do mesmo. Para evitar que existam inúmeras classes diferentes de café, é utilizado o padrão Decorator. Inicialmente, quando um pedido de café é feito, é instanciado um objeto referente ao sabor do café. Em seguida, sempre que um condimento for solicitado para ser incluı́do no café, é instanciado um objeto da classe referente ao condimento e este objeto realiza a “decoração” do objeto sabor, aumentando a descrição do pedido e seu referido valor. Desta forma é possivel pedir diversos tipos diferentes de sabores com diversos condimentos e combinações diferentes de condimentos. Cada inclusão de um novo condimento faz com que o objeto referente ao sabor do café seja incrementado. Este é o principal Padrão Decorator 84 Figura 4.10: Diagrama de classes do exemplo para o padrão Decorator. aspecto do padrão Decorator. O pedido vai sendo incrementado em tempo de execução. Isto reduz em grande escala a quantidade de classes utilizada, torna a solução mais viável e permite, inclusive, que sejam feitas diversas inclusões do mesmo condimento em um único pedido de café. 4.5.2 Comparação das implementações em Java e ooErlang Em ambas as implementações (Java e ooErlang), todos os tipos de sabores diferentes de café estendem a classe Beverage, que é a classe geral dos tipos de bebidas. Cada tipo de bebida implementa o método cost(), pois cada bebida possui seu próprio sabor. Cada diferente tipo de bebida possui uma diferente descrição, que é definida no momento da instanciação de um objeto de alguma das classes de bebidas. Os códigos 4.5.1 e 4.5.2 mostram a implementação da classe Espresso em Java e ooErlang Na instanciação do objeto Espresso, o atributo description apenas recebe um novo valor, de acordo com o sabor da bebida, neste caso o café do tipo “expresso”. Para cada tipo de Padrão Decorator 1 85 public class Espresso extends Beverage { 2 public Espresso() { description = "Espresso"; } 3 4 5 6 public double cost() { return 1.99; } 7 8 9 10 } 11 Código 4.5.1: Classe Espresso em Java 1 2 3 4 -class(expresso). -extends(beverage). -export([new/0, cost/0]). -constructor([new/0]). 5 6 methods. 7 8 9 new() -> self::Description = "Expresso". 10 11 cost() -> 1.99. Código 4.5.2: Classe Expresso em ooErlang bebida, a implementação é semelhante, sendo que as principais diferenças são as descrições das bebidas e o valor de cada uma. Em relação aos condimentos, cada condimento também possui implementações semelhantes. Todas as classes que pertencem ao tipo condimento implementam a classe CondimentDecorator, que possui a descrição total de cada pedido a ser feito. Além disso cada classe referente a um tipo de condimento recebe como parâmetro um objeto do tipo bebida, e aumenta sua descrição e seu preço. Desta forma um objeto está sendo modificado em tempo de execução. Os códigos 4.5.3 e 4.5.4 mostram a implementação da classe Soy em Java e ooErlang. Conforme pode ser observado, a classe Soy (referente ao condimento soja) já possui um atributo do tipo Beverage que pode receber qualquer instância de uma classe pertencente aos diferentes sabores de cafés. Nesta classe, tanto o método que trata da manipulação da descrição da bebida quanto o método que retorna o valor da bebida, incrementam o que existia na bebida recebida durante a instanciação da classe de condimentos. Trabalhando Padrão Decorator 1 2 86 public class Soy extends CondimentDecorator { Beverage beverage; 3 public Soy(Beverage beverage) { this.beverage = beverage; } 4 5 6 7 public String getDescription() { return beverage.getDescription() + ", Soy"; } 8 9 10 11 public double cost() { return .15 + beverage.cost(); } 12 13 14 15 } Código 4.5.3: Classe Soy em Java 1 2 3 4 -class(soy). -extends(condimentDecorator). -export([new/1, get_description/0, add_cost/0]). -constructor([new/1]). 5 6 attributes. 7 8 Beverage. 9 10 methods. 11 12 13 new(Beverage) -> self::Beverage = Beverage. 14 15 16 17 get_description() -> Temp = self::Beverage, Temp::get_description() ++ ", Soy". 18 19 20 21 add_cost() -> Temp = self::Beverage, 0.15 + Temp::cost(). Código 4.5.4: Classe Soy em ooErlang com esta implementação, a cada novo incremento de condimento, existe incremento na descrição e no valor da bebida. Portanto, com a utilização desta forma de modelagem é possivel incluir inúmeros tipos de condimentos a uma única bebida, com ou sem repetição. No final, todos os preços dos condimentos reunidos são somados juntamente com o preço da bebida pura. A classe principal deste exemplo, StarbuzzCoffee possui esta denominação pois refere-se ao nome Padrão Decorator 87 fictı́cio desta loja de cafés. Na implementação da classe principal StarbuzzCoffee, são feitas instanciações de três (3) tipos diferentes de bebidas. No primeiro caso, não foi incluı́do nenhum condimento na bebida escolhida. No segundo caso foram incluı́dos condimentos diferentes na bebida, verificando seu preço final. E no terceiro caso, além de serem incluı́dos condimentos, um dos condimentos foi incluı́do duas vezes. Os códigos mostrados em 4.5.5 e 4.5.6 mostram a implementação desta classe em Java e ooErlang. 1 public class StarbuzzCoffee { 2 public static void main(String args[]) { Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); 3 4 5 6 7 Beverage beverage2 = new DarkRoast(); beverage2 = new Mocha(beverage2); beverage2 = new Mocha(beverage2); beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $" + beverage2.cost()); 8 9 10 11 12 13 14 Beverage beverage3 = new HouseBlend(); beverage3 = new Soy(beverage3); beverage3 = new Mocha(beverage3); beverage3 = new Whip(beverage3); System.out.println(beverage3.getDescription() + " $" + beverage3.cost()); 15 16 17 18 19 20 } 21 22 } Código 4.5.5: Classe principal StarbuzzCoffee em Java Percebe-se que, no código 4.5.5 da classe principal, em relação aos pedidos da segunda e terceira bebida, sempre que um novo condimento vai ser incluı́do no pedido, ocorre a instanciação do objeto referente ao condimento, sendo que nesta instanciação, a própria bebida que irá receber o condimento é passada como parâmetro de instanciação do condimento. É desta forma que o objeto vai sendo “decorado” em tempo de execução. No código 4.5.6 para cada inclusão de um novo condimento, um novo objeto é criado, sendo que este novo objeto recebe todas as modificações realizadas anteriormente. Esta implementação foi feita desta forma devido à caracterı́stica própria do Erlang, que não permite a uma variável a mudança de seu valor em tempo de execução. Portanto, a idéia Padrão Decorator 1 2 88 -class(starbuzzCoffee). -export([main/0]). 3 4 class_methods. 5 6 7 8 main() -> Beverage = expresso::new(), io:format("~p $ ~p~n", [Beverage::get_description(), Beverage::cost()]), 9 10 11 12 13 14 BevTemp1 = darkRoast::new(), BevTemp2 = mocha::new(BevTemp1), BevTemp3 = mocha::new(BevTemp2), BevTemp4 = whip::new(BevTemp3), io:format("~p $ ~p~n", [BevTemp4::get_description(), BevTemp4::cost()]), 15 16 17 18 19 BevTemp5 BevTemp6 BevTemp7 BevTemp8 = = = = houseBlend::new(), soy::new(BevTemp5), mocha::new(BevTemp6), whip::new(BevTemp7), 20 21 io:format("~p $ ~p~n", [BevTemp8::get_description(), BevTemp8::cost()]). Código 4.5.6: Classe principal StarbuzzCoffee em ooErlang central do padrão Decorator ainda persiste nesta implementação. As figuras 4.11 e 4.12 mostram a execução deste exemplo em Java e ooErlang, respectivamente. Na implementação do exemplo da loja de cafés, que trata do padrão de projetos Decorator, é possivel perceber que a extensão ooErlang mostra-se apta a realizar uma modelagem com os mesmos conceitos presentes na implementação da linguagem Java. Os resultados são comprovados na verificação do resultado da execução deste exemplo nas duas linguagens, mostrando saı́das semelhantes. Desta forma pode-se afirmar que os fontes compilados são equivalentes. A implementação em ooErlang apresenta-se bastante semelhante à realizada em Java. A única diferença observada na codificação deste exemplo ocorre na inclusão de condimentos às bebidas que estavam sendo pedidas. Enquanto que em Java é utilizado o mesmo objeto para receber um novo condimento, em ooErlang, torna-se necessário criar um novo objeto para cada inclusão de um novo condimento. Mas este detalhe não modifica o resultado final, continuando a implementar a idéia central do padrão Decorator: modificar um objeto, atribuı́ndo-lhe novas responsabilidades em tempo de execução. Na utilização do padrão de projetos Decorator, percebe-se, de um modo geral, que o sistema a utilizá-lo ganha mais flexibilidade do que utilizando heranças estáticas (no exemplo Padrão Facade 89 Figura 4.11: Execução do exemplo para o padrão Decorator em Java. StarbuzzCoffee, a utilização de herança para os condimentos seria uma solução impraticável). Também observa-se que um objeto “decorado” é diferente do objeto originalmente instanciado. Um sistema que se utilize deste padrão de projetos irá ocasionalmente passar a possuir pequenos objetos “decoradores”, como consequência. No caso da loja StarbuzzCoffee, esses objetos são os condimentos. 4.6 Padrão Facade Conforme o próprio nome sugere, este padrão tem por finalidade principal a de: “Oferecer uma interface única para um conjunto de interfaces de um subsistema. Facade define uma interface de nı́vel mais elevado que torna o subsistema mais fácil de usar” [Gamma et al., 1994]. Este padrão possui grande possibilidade de aplicação nas situações em que Padrão Facade 90 Figura 4.12: Execução do exemplo para o padrão Decorator em ooErlang. um sistema possui diversas interfaces que realizam diferentes tarefas. Desta forma este padrão é bastante utilizado para facilitar o usuário, que realiza suas tarefas de uma forma mais fácil ao utilizar-se de apenas uma interface simples, que abstrai o trabalho de interagir com diferentes interfaces. Assim é possivel prover uma simples interface para um subsistema complexo. Outra vantagem na aplicação do padrão Facade é na necessidade de portabilidade de sistemas, pois a utilização deste padrão diminui as dependências entre clientes e as implementações de classes abstratas. 4.6.1 Exemplo utilizado para este padrão Para este padrão foi modelado um sistema retirado de [Freeman et al., 2004] que controla as operações a serem utilizadas em um Home Theater. Verifica-se que esse sistema de áudio e vı́deo possui diferentes equipamentos com os quais é possivel operar. Cada equipamento foi modelado como pertendo a uma classe, sendo eles: Amplifier (amplificador), CdPlayer (reprodutor de CD), DvdPlayer (reprodutor de DVD), Tuner (sintonizador de rádio), PopcornPopper (máquina de pipocas), Projector (projetor), Screen (tela) e TheaterLights (luzes do ambiente). Padrão Facade 91 Se, por exemplo, esse sistema estivesse modelado com todas as suas interfaces separadas, para um usuário assistir a um filme, várias ações deveriam ser executadas (Ligar a máquina de pipocas, retirar as pipocas, ajustar luzes do ambiente, baixar tela de projeção, ligar projetor, etc.) Todas essas ações seriam necessárias para apenas assistir a um filme. Verifica-se então a necessidade de aplicar um padrão de projetos que facilite a utilização do usuário para com o projeto como um todo, fazendo com que uma interface simples possa facilitar a manipulação do sistema e de suas funcionalidades. Este cenário refere-se ao padrão Facade, que utiliza-se de uma interface (conhecida como “fachada”) com o objetivo de simplificar a realização de ações que o sistema possa realizar. A figura 4.13 mostra um diagrama de classes para melhor entendimento do referido exemplo. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Verifica-se que todas as classes correspondentes a cada equipamento ou controlador está ligada com a classe que funciona como a fachada do sistema, chamada de HomeTheaterFacade. Se, por acaso, um usuário desejar assistir a um filme, ou simplesmente ouvir um CD ou o rádio, na classe HomeTheaterFacade foram definidos métodos que realizam estas ações facilmente, simplificando a quantidade de tarefas que o usuário precisa realizar para se chegar a um objetivo. 4.6.2 Comparação das implementações em Java e ooErlang Cada classe correspondente a um equipamento pode realizar diferentes ações neste dispositivo. Por exemplo, a classe CdPlayer pode realizar as seguintes ações no aparelho reprodutor de CD: ligar, desligar, ejetar, reproduzir faixa, pausar/parar reprodução etc. E esta quantidade de ações existe em cada um dos equipamentos. Desta forma é fácil verificar a complexidade do sistema sem a utilização do padrão Facade. Para este cenário é explicado o caso particular em que se deseja assistir a um filme. Inicialmente será observada a classe DvdPlayer. Esta classe possui os principais comandos que podem ser realizados no aparelho reprodutor de DVD do sistema utilizado, tais como ligar, desligar, ejetar DVD, reproduzir filme etc. Mas além dessas ações, é necessário perceber que outras classes devem ser trabalhadas para que se realize a ação de assistir a um filme, como, por exemplo, ajustar tela, ligar projetor, entre outras. Padrão Facade 92 Figura 4.13: Diagrama de classes do exemplo para o padrão Facade. Observando inicialmente a classe DvdPlayer, verificando apenas alguns de seus métodos implementados em Java no código 4.6.1 e em ooErlang no código mostrado em 4.6.2, observamos que os métodos referentes ao dispositivo reprodutor de DVD foram implementados normalmente, apenas para a utilização em objetos desta classe. Os fontes mostrados são semelhantes em sua idéia principal tanto em Java quanto em ooErlang, observadas as diferenças de sintaxe de ambas as linguagens. Mas para que seja possı́vel assistir a um filme, é preciso que todos os aparelhos a serem utilizados neste Padrão Facade 1 2 3 4 5 93 public class DvdPlayer { String description; int currentTrack; Amplifier amplifier; String movie; 6 7 8 9 public void on() { System.out.println(description + " on"); } 10 11 12 13 public void off() { System.out.println(description + " off"); } 14 15 16 17 18 public void eject() { movie = null; System.out.println(description + " eject"); } 19 20 21 22 23 24 public void play(String movie) { this.movie = movie; currentTrack = 0; System.out.println(description + " playing \"" + movie + "\""); } Código 4.6.1: Classe DvdPlayer em Java processo sejam ligados e configurados. Este é justamente o trabalho realizado na classe HomeTheaterFacade, na qual cada ação especı́fica (reproduzir música, ouvir rádio, ver filme etc) é implementada nesta classe. Os códigos mostrados em 4.6.3 e 4.6.4 mostram a implementação da classe HomeTheaterFacade em Java e ooErlang. Nas implementações vistas em 4.6.3 e 4.6.4 verifica-se que apenas o método responsável por gerenciar todas as atividades relativas à ação de ver um filme foi mostrado. Os outros métodos, referentes à outras ações foram abstraı́dos para melhor entendimento da utilização do padrão Facade. O método implementado nesta classe para que se possa assistir à um filme chama-se “watchMovie()” que recebe como parâmetro o nome do filme. Verifica-se então que, se não estivesse sendo utilizada uma classe para funcionar como simplificadora de interfaces (Facade), seria necessário chamar métodos referentes às classes PopcornPopper, TheaterLights, Screen, Projector, Amplifier e DvdPlayer. Graças à classe HomeTheaterFacade, todos esses métodos são chamados dentro do método responsável por preparar um filme a ser assistido. Assim, o usuário tem muito menos trabalho para poder realizar uma ação utilizando deste sistema. Padrão Facade 1 2 3 4 94 -class(dvdPlayer). -export([new/2, on/0, off/0, eject/0, play/1, stop/0, pause/0]). -export([set_two_channel_audio/0, set_surround_audio/0, to_string/0]). -constructor([new/2]). 5 6 attributes. 7 8 9 10 11 Description; CurrentTrack; Amplifier; Movie. 12 13 methods. 14 15 16 on() -> io:format("~p on~n", [self::Description]). 17 18 19 off() -> io:format("~p off~n", [self::Description]). 20 21 22 23 eject() -> self::Movie = "No title", io:format("~p eject~n", [self::Description]). 24 25 26 27 28 play(Movie) -> self::Movie = Movie, self::CurrentTrack = 0, io:format("~p playing ~p~n", [self::Description, self::Movie]). Código 4.6.2: Classe DvdPlayer em ooErlang A classe principal deste exemplo chama-se HomeTheaterTestDrive. Ao ser executada, esta classe inicialmente instancia objetos para todas as classes referentes aos equipamentos do sistema de áudio e vı́deo modeladas. Em seguida é instanciado um objeto referente à classe HomeTheaterFacade, que recebe como parâmetros de entrada todos os objetos anteriormente instanciados. A classe HomeTheaterTestDrive é mostrada nos códigos 4.6.5 e 4.6.6, em Java e ooErlang, respectivamente. Após todos os objetos de cada classe que representa um equipamento serem instanciados, e o objeto referente à classe HomeTheaterFacade também ser instanciado, este último objeto pode ser utilizado para realizar as ações complexas destes equipamentos de forma simples e rápida. No caso observado, é utilizado apenas o método para assistir a um filme. Verifica-se que qualquer ação relativa a este sistema pode ser facilmente executada utilizando-se dos métodos implementados na classe HomeTheaterFacade. As figuras mostradas em 4.14 e 4.15 mostram, respectivamente, a execução deste exemplo em Java e Padrão Facade 1 2 3 4 5 6 7 8 9 95 public class HomeTheaterFacade { Amplifier amp; Tuner tuner; DvdPlayer dvd; CdPlayer cd; Projector projector; TheaterLights lights; Screen screen; PopcornPopper popper; 10 public void watchMovie(String movie) { System.out.println("Get ready to watch a movie..."); popper.on(); popper.pop(); lights.dim(10); screen.down(); projector.on(); projector.wideScreenMode(); amp.on(); amp.setDvd(dvd); amp.setSurroundSound(); amp.setVolume(5); dvd.on(); dvd.play(movie); } 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Código 4.6.3: Classe HomeTheaterFacade em Java ooErlang. Foi possı́vel observar que o padrão de projetos Facade mostrou-se implementável na extensão orientada a objetos ooErlang. Este padrão visa facilitar a utilização de um sistema que seja complexo, geralmente possuindo diferentes interfaces. No exemplo utilizado, para cada ação a ser realizada era necessário chamar diversos métodos de diferentes classes, dificultando as ações do usuário. Outro detalhe a ser observado é o de que, no exemplo utilizado, ao invés de existirem diversas interfaces, existem diferentes classes, uma para cada tipo de dispositivo. Mas a utilização do padrão Facade é semelhante, tanto na simplificação de classes quanto de interfaces. Observa-se que, nenhuma classe foi modificada para que este padrão pudesse ser utilizado. Apenas utilizou-se uma classe principal que realiza todas as pequenas ações para se concluir cada tarefa peculiar desse exemplo. Uma das facilidades verificadas ao se utilizar do padrão Facade, conforme visto anteriormente, é a de proteger o cliente dos componentes do subsistema, reduzindo notavelmente Padrão Facade 1 2 3 4 96 -class(homeTheaterFacade). -export([new/8, watch_movie/1, end_movie/0, listen_to_cd/1, end_cd/0]). -export([listen_to_radio/1, end_radio/0]). -constructor([new/8]). 5 6 attributes. 7 8 9 10 11 12 13 14 15 Amp; Tuner; Dvd; Cd; Projector; Lights; Screen; Popper. 16 17 methods. 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 watch_movie(Movie) -> io:format("Get ready to watch a movie...~n"), Temp1 = self::Popper, Temp1::on(), Temp1::pop(), Temp2 = self::Lights, Temp2::dim(10), Temp3 = self::Screen, Temp3::down(), Temp4 = self::Projector, Temp4::on(), Temp4::widescreen_mode(), Temp5 = self::Amp, Temp5::on(), Temp5::set_dvd(self::Dvd), Temp5::set_surround_sound(), Temp5::set_volume(5), Temp6 = self::Dvd, Temp6::on(), Temp6::play(Movie). Código 4.6.4: Classe HomeTheaterFacade em ooErlang a quantidade de objetos com os quais o cliente passa a interagir. Dessa forma é promovido um acoplamento frágil entre os subsistemas e seus clientes. Isto significa dizer que os componentes de um subsistema podem ser modificados sem que isto afete diretamente os clientes. Padrão Flyweight 1 2 3 4 5 6 7 8 9 10 97 public class HomeTheaterTestDrive { public static void main(String[] args) { Amplifier amp = new Amplifier("Top-O-Line Amplifier"); Tuner tuner = new Tuner("Top-O-Line AM/FM Tuner", amp); DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD Player", amp); CdPlayer cd = new CdPlayer("Top-O-Line CD Player", amp); Projector projector = new Projector("Top-O-Line Projector", dvd); TheaterLights lights = new TheaterLights("Theater Ceiling Lights"); Screen screen = new Screen("Theater Screen"); PopcornPopper popper = new PopcornPopper("Popcorn Popper"); 11 HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, dvd, cd, projector, screen, lights, popper); 12 13 14 15 homeTheater.watchMovie("Raiders of the Lost Ark"); homeTheater.endMovie(); 16 17 } 18 19 } Código 4.6.5: Classe principal HomeTheaterTestDrive em Java 1 2 -class(homeTheaterTestDrive). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 10 11 12 13 14 main() -> Amp = amplifier::new("Top-O-Line Amplifier"), Tuner = tuner::new("Top-O-Line AM/FM Tuner", Amp), Dvd = dvdPlayer::new("Top-O-Line DVD Player", Amp), Cd = cdPlayer::new("Top-O-Line CD Player", Amp), Projector = projector::new("Top-O-Line Projector", Dvd), Lights = theaterLights::new("Theater Ceiling Lights"), Screen = screen::new("Theater Screen"), Popper = popcornPopper::new("Popcorn Popper"), 15 HomeTheater = homeTheaterFacade::new(Amp,Tuner,Dvd,Cd, Projector,Screen,Lights,Popper), 16 17 18 HomeTheater::watch_movie("Raiders of the Lost Ark"), HomeTheater::end_movie(). 19 20 Código 4.6.6: Classe principal HomeTheaterTestDrive em ooErlang 4.7 Padrão Flyweight Este padrão de projeto, segundo sua definição, tem por objetivo: “Usar compartilhamento para suportar grandes quantidades de objetos refinados eficientemente” [Gamma et al., 1994]. Existem casos em que é necessário a um sistema ter que instanciar e gerenciar Padrão Flyweight 98 Figura 4.14: Execução do exemplo para o padrão Facade em Java. uma grande quantidade de objetos. Para uma quantidade muito elevada, o desempenho do sistema pode diminuir, em virtude de um custo elevado de memória utilizada. Para situações como essa que o padrão Flyweight é utilizado. Este padrão pode ser bem aplicado a problemas em que um sistema tenha que utilizar diversas instâncias de objetos, conforme dito anteriormente. Outra caracterı́stica do padrão Flyweight é que sua utilização depende da possibilidade de se poder substituir muitos grupos de objetos por uma pequena quantidade de objetos compartilhados e, além disso, que o sistema a ser modelado não dependa da identidade do objeto, já que haverá compartilhamento de objetos. Padrão Flyweight 99 Figura 4.15: Execução do exemplo para o padrão Facade em ooErlang. 4.7.1 Exemplo utilizado para este padrão No exemplo utilizado, retirado de [Truett, 2013], é modelado um sistema para gerenciar um restaurante que serve chás. Este é o único tipo de bebida disponı́vel neste estabelecimento. Entretanto, existem diferentes sabores de chás que podem ser pedidos. Para cada cliente vai existir uma valor representando a mesa utilizada, sendo que qualquer cliente que já estiver presente no restaurante poderá fazer um pedido de um tipo especı́fico de chá. Existe uma classe chamada TeaFlavor, responsável por armazenar o sabor do chá especı́fico para cada instância desta classe. Esta classe também implementa um método da classe abstrata TeaOrder. Outra classe implementada chama-se TeaOrderContext, responsável por armazenar os dados das mesas dos clientes. A classe TeaRestroom possui os dados das mesas dos clientes e dos pedidos realizados. Finalmente a classe TeaFlavorFactory implementa a idéia principal do padrão Fyweight. A figura 4.16 mostra um diagrama de classes deste exemplo. Pode-se obervar que, dependendo dos testes a serem realizados, a quantidade de pedidos pode se tornar muito elevada. Cada pedido de chá é, em teoria, um novo objeto instanciado da classe TeaFlavor. Todavia, este padrão possibilita a reutilização de objetos previamente Padrão Flyweight 100 Figura 4.16: Diagrama de classes do exemplo para o padrão Flyweight. instanciados. Sempre que um tipo de sabor é pedido, o mesmo é armazenado e um objeto deste sabor é instanciado e retornado. Porém se um sabor pedido já tiver sido previamente instanciado, o mesmo é apenas retornado, sem que haja necessidade de instanciação de objetos. Esse controle é realizado na classe TeaFlavorFactory. 4.7.2 Comparação das implementações em Java e ooErlang Todas as classes mostradas neste exemplo foram implementadas em ooErlang para verificar seu funcionamento em comparação com a implementação em Java. Analisando primeiramente os códigos mostrados em 4.7.1 e 4.7.2, que são as implementações em Java e ooErlang da classe TeaFlavor, verifica-se que esta classe possui apenas um atributo, referente ao nome do sabor da bebida pedida, e possui métodos para retornar o sabor da bebida e servir os pedidos realizados. Além dos métodos mostrados, esta classe também possui um construtor para a instan- Padrão Flyweight 1 2 101 public class TeaFlavor extends TeaOrder { String teaFlavor; 3 TeaFlavor(String teaFlavor) { this.teaFlavor = teaFlavor; } 4 5 6 7 public String getFlavor() { return this.teaFlavor; } 8 9 10 11 public void serveTea(TeaOrderContext teaOrderContext) { System.out.println("Serving tea flavor " + teaFlavor + " to table number " + teaOrderContext.getTable()); } 12 13 14 15 16 17 18 } Código 4.7.1: Classe TeaFlavor em Java 1 2 3 4 -class(teaFlavor). -extends(teaOrder). -export([new/1, get_flavor/0, serve_tea/1]). -constructor([new/1]). 5 6 attributes. 7 8 TeaFlavor. 9 10 methods. 11 12 13 new(TeaFlavor) -> self::TeaFlavor = TeaFlavor. 14 15 16 get_flavor() -> self::TeaFlavor. 17 18 19 20 serve_tea(TeaOrderContext) -> io:format("Serving tea flavor ~p to table number ~p~n", [self::TeaFlavor, TeaOrderContext::get_table()]). Código 4.7.2: Classe TeaFlavor em ooErlang ciação de novos sabores (utilizado em sabores ainda não pedidos anteriormente). A classe TeaOrder é uma classe abstrata e possui apenas um método, implementado na classe TeaFlavor. O método responsável por servir os diferentes tipos de bebidas que foram solicitadas pelos clientes chama-se serveTea, recebendo como parâmetro de entrada a mesa na qual este pedido foi realizado. Padrão Flyweight 102 A classe TeaFlavorFactory, mostrada nos códigos 4.7.3 e 4.7.4 (Java e ooErlang respectivamente), possui como atributos um arranjo que armazena todos os diferentes sabores pedidos pelos clientes. Outro atributo desta classe é a quantidade de chás que foi realmente instanciada, valor diferente da quantidade de chás servidos. O método getTotalTeaFlavorsMade(), também implementado na classe TeaFlavorFactory, retorna a quantidade de objetos do tipo TeaFlavor de fato instanciada. 1 2 public class TeaFlavorFactory { TeaFlavor[] flavors = new TeaFlavor[10]; 3 int teasMade = 0; 4 5 public TeaFlavor getTeaFlavor(String flavorToGet) { if (teasMade > 0) { for (int i = 0; i < teasMade; i++) { if (flavorToGet.equals((flavors[i]).getFlavor())) { return flavors[i]; } } } flavors[teasMade] = new TeaFlavor(flavorToGet); return flavors[teasMade++]; } 6 7 8 9 10 11 12 13 14 15 16 17 public int getTotalTeaFlavorsMade() { return teasMade; } 18 19 20 21 } Código 4.7.3: Classe TeaFlavorFactory em Java O método getTeaFlavor() que recebe como parâmetro de entrada um sabor de chá a ser servido para o cliente, é o responsável por instanciar novos objetos do tipo chá ou reaproveitar objetos anteriormente instanciados. Sempre que este método é invocado, o sabor do chá que é recebido como parâmetro de entrada, é procurado na lista de sabores já instanciados (atributo flavors, arranjo com os tipos de sabores). Se este sabor não for encontrado, o mesmo é instanciado, armazenado na lista de sabores e retornado. Caso o objeto já exista na lista, ele é apenas retornado. Desta forma, mesmo que vários clientes peçam muitos sabores, sendo eles repetidos ou não, a quantidade de objetos a ser instanciada vai ser menor que a quantidade de pedidos, devido aos sabores repetidos. Assim o padrão Flyweight realiza seu objetivo de compartilhar objetos para gerenciar uma grande quantidade de objetos. Isto significa dizer que, havendo Padrão Flyweight 1 2 3 103 -class(teaFlavorFactory). -export([new/0, get_tea_flavor/1, get_total_tea_flavors_made/0]). -constructor([new/0]). 4 5 attributes. 6 7 8 Flavors; TeasMade. 9 10 methods. 11 12 13 14 new() -> self::Flavors = [], self::TeasMade = 0. 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 get_tea_flavor(FlavorToGet) -> TeasMade = self::TeasMade, if (TeasMade > 0) -> {Return, Position} = aux_is_item(FlavorToGet, self::Flavors), if (Return == true) -> Flavor = aux_get_item(Position, self::Flavors), Flavor; true -> {NewList, PositionInserted} = aux_insert_new(FlavorToGet, self::Flavors), Flavor = aux_get_item(PositionInserted, NewList), self::Flavors = NewList, self::TeasMade = self::TeasMade+1, Flavor end; true -> {NewList, PositionInserted} = aux_insert_new(FlavorToGet, self::Flavors), Flavor = aux_get_item(PositionInserted, NewList), self::Flavors = NewList, self::TeasMade = self::TeasMade+1, Flavor end. Código 4.7.4: Classe TeaFlavorFactory em ooErlang uma quantidade limitada de sabores, assim como diversos pedidos, a quantidade final de objetos instanciada é relativamente pequena, por estar relacionada com os sabores sem repetição e não com a quantidade de pedidos. A classe TeaRestroom possui como atributos dois arranjos, que armazenam tanto os pedidos de cada cliente quanto as mesas referentes a cada pedido realizado. São implementados dois métodos principais, um para retornar a quantidade real de objetos do tipo Padrão Flyweight 104 TeaFlavor que foram instanciados, e outro para gerenciar os diferentes pedidos que são feitos pelos clientes. Os códigos mostrados em 4.7.5 e 4.7.6 mostram a implementação desta classe (TeaRestroom) em Java e ooErlang respectivamente. 1 2 3 4 5 public class TeaRestroom { TeaFlavor[] flavors = new TeaFlavor[100]; TeaOrderContext[] tables = new TeaOrderContext[100]; int ordersMade = 0; TeaFlavorFactory teaFlavorFactory; 6 public void takeOrders(String flavorIn, int table) { flavors[ordersMade] = teaFlavorFactory.getTeaFlavor(flavorIn); tables[ordersMade++] = new TeaOrderContext(table); } 7 8 9 10 11 public void showTotalTeaFlavorsMade(){ System.out.println("Total tea Flavor Objects made: " + teaFlavorFactory.getTotalTeaFlavorsMade()); } 12 13 14 15 16 } Código 4.7.5: Classe TeaRestroom em Java Ao se testar este exemplo, é utilizada a classe principal TestFlyweight. Nesta classe, no método principal, inicialmente são instanciados objetos das classes TeaRestroom e TeaFlavorFactory. Esses objetos são necessários para que se possam realizar os pedidos de chás dos clientes. Em seguida são feitos vários pedidos, para diferentes mesas, utilizando-se do objeto do tipo TeaRestroom. Em seguida, para cada pedido feito é chamado um método responsável por servir os pedidos realizados e, para finalizar, é mostrada a quantidade de objetos do tipo TeaFlavor que foram de fato instanciadas. As figuras 4.17 e 4.18 mostram as execuções em Java e ooErlang, respectivamente. Verifica-se, observando as execuções em Java e ooErlang do exemplo para o padrão de projetos Flyweight que, de fato, foram feitos diversos pedidos para diferentes mesas. Mas, de acordo com a implementação para este padrão, sempre que um pedido requerido já tivesse sido instanciado anteriormente, o mesmo objeto seria devolvido. Dessa forma houve uma economia de memória por parte da aplicação e foi possı́vel verificar que a quantidade real de objetos instanciados é bem inferior à quantidade de pedidos realizados. A extensão orientada a objetos ooErlang mostra-se, de acordo com os resultados obtidos, capaz de implementar o padrão de projetos Flyweight. A execução das implementações Padrão Flyweight 1 2 3 105 -class(teaRestroom). -export([new/0, take_orders/2, show_total_tea_flavors_made/0]). -constructor([new/0]). 4 5 attributes. 6 7 8 9 10 Flavors; Tables; OrdersMade; TeaFlavorFactory. 11 12 methods. 13 14 15 16 17 18 new() -> self::Flavors = [], self::Tables = [], self::OrdersMade = 0, self::TeaFlavorFactory = teaFlavorFactory::new(). 19 20 21 22 23 24 take_orders(FlavorIn, Table) -> Temp = self::TeaFlavorFactory, self::Flavors = self::Flavors ++ [Temp::get_tea_flavor(FlavorIn)], self::Tables = self::Tables ++ [teaOrderContext::new(Table)], self::OrdersMade = self::OrdersMade + 1. 25 26 27 28 29 show_total_tea_flavors_made() -> Temp = self::TeaFlavorFactory, io:format("Total tea Flavor Objects made: ~p~n", [Temp::get_total_tea_flavors_made()]). Código 4.7.6: Classe TeaRestroom em ooErlang em ambas as linguagens apresentou-se obtendo o mesmo resultado. Este padrão está sendo aplicado para gerenciar a instanciação de objetos do tipo TeaFlavor, utilizando-se do compartilhamento de objetos para facilitar a utilização de objetos repetidos sem comprometer o desempenho da aplicação. Caso o padrão de projetos Flyweight não estivesse sendo utilizado, para cada novo pedido de um cliente, independente da mesa do cliente que estivesse solicitando um chá, iria haver uma nova instanciação de objetos. Mesmo que os objetos fossem similares e que não houvesse necessidade de identificação do objeto para com o sistema, eles estariam sendo criados e, dependendo da quantidade de pedidos feitos, o desempenho do sistema poderia estar comprometido. Ao utilizar-se do padrão Flyweight, este problema é contornado. Em alguns casos, na utilização do padrão Flyweight, os objetos que, para o usuário, podem possuir uma quantidade elevada de instâncias, precisam ser salvos de alguma forma. Padrão Proxy 106 Figura 4.17: Execução do exemplo para o padrão Flyweight em Java. Verifica-se, desta forma, as vantagens de se utilizar deste padrão pois, irá haver redução na quantidade real de dados dos objetos compartilhados a serem salvos, tendo como consequência uma economia no espaço de dados a ser utilizado para a persistência de alguma informação de um sistema que se utilize do padrão de projetos Flyweight. 4.8 Padrão Proxy De acordo com sua definição inicial, o padrão de projetos Proxy tem por objetivo principal: “Prover um substituto ou ponto através do qual um objeto possa controlar o acesso a outro” [Gamma et al., 1994]. Em muitos casos este padrão de projetos é utilizado principalmente quando é necessário que haja uma comunicação remota entre duas máquinas que estejam utilizando um mesmo sistema. A utilização deste padrão tem por finalidade Padrão Proxy 107 Figura 4.18: Execução do exemplo para o padrão Flyweight em ooErlang. principal a de facilitar o controle de acesso entre determinados objetos de um sistema. Semelhante a outros padrões de projeto, o padrão Proxy pode ser utilizado em diferentes situações, mas em todas elas, existem uma ou mais classes que funcionam como sendo um ponto de acesso para determinado objeto. Os principais tipos de objetos “Proxy” são: Proxy remoto, utilizado para controlar acesso a um objeto remoto, ou seja, localizado em outra máquina, Proxy virtual, que tem por objetivo criar e acessar objetos complexos de forma simplificada e Proxy de proteção, cuja finalidade é controlar o acesso a um objeto original, geralmente possuidor de diferentes direitos de acesso. 4.8.1 Exemplo utilizado para este padrão Para demonstrar a utilização do padrão Proxy, é utilizado um exemplo retirado de [Books, 2013], no qual é implementado um sistema que simula o carregamento de arquivos de imagem para visualização do usuário. Nesse sistema, são instanciados objetos para receber as imagens a serem carregadas e, dessa forma, o usuário poder visualizá-las. Logicamente, considerando que a implementação deste exemplo é apenas uma simulação da utilização do padrão Proxy, não são utilizadas imagens reais, apenas cadeias de Padrão Proxy 108 caracteres para funcionarem como se fossem imagens carregadas no sistema. A figura 4.19 mostra, com mais detalhes, um diagrama de classes da implementação deste exemplo, para melhor entendimento. Figura 4.19: Diagrama de classes do exemplo para o padrão Proxy. 4.8.2 Comparação das implementações em Java e ooErlang Conforme pode ser visto pelo diagrama mostrado em 4.19, é implementada uma simples interface chamada Image, com a assinatura de um método que tem por responsabilidade mostrar uma imagem para o usuário ser capaz de visualizar. Este método é implementado por duas classes, RealImage e ProxyImage. Ambas as classes devem possuir a implementação deste método, já que estendem esta interface. Os códigos mostrados em 4.8.1 e 4.8.2 apresentam as implementações da classe RealImage em Java e ooErlang, nesta ordem. Levando-se em consideração que este exemplo é de um sistema visualizador de imagens que utiliza o padrão de projetos Proxy, percebe-se que, cada objeto que representa uma imagem deve ser instância de uma classe de imagens, neste caso, RealImage. Esta classe representa o objeto imagem propriamente dito. É o objeto que passa a ter seu acesso intermediado pela classe “Proxy”, que neste exemplo é a classe ProxyImage. Padrão Proxy 1 109 class RealImage implements Image { 2 private String filename = null; 3 4 public RealImage(final String FILENAME) { filename = FILENAME; loadImageFromDisk(); } 5 6 7 8 9 private void loadImageFromDisk() { System.out.println("Loading " + filename); } 10 11 12 13 public void displayImage() { System.out.println("Displaying " + filename); } 14 15 16 17 } Código 4.8.1: Classe RealImage em Java 1 2 3 4 -class(realImage). -implements(image). -export([new/1, load_image_from_disk/0, display_image/0]). -constructor([new/1]). 5 6 attributes. 7 8 Filename. 9 10 methods. 11 12 13 14 new(Filename) -> self::Filename = Filename, load_image_from_disk(). 15 16 17 load_image_from_disk() -> io:format("Loading ~p~n",[self::Filename]). 18 19 20 display_image() -> io:format("Displaying ~p~n", [self::Filename]). Código 4.8.2: Classe RealImage em ooErlang A classe RealImage possui como atributo o nome da imagem que cada instância desta classe deve possuir. Além disso, a classe possui um construtor e outros dois métodos. Um deles tem por objetivo simular o carregamento da imagem no disco rı́gido do computador (loadImageFromDisk()) e o outro é especificamente o método responsável por mostrar a imagem ao usuário (displayImage()). Os códigos mostrados em 4.8.3 e 4.8.4 mostram a Padrão Proxy 110 implementação em Java e ooErlang da classe ProxyImage. 1 class ProxyImage implements Image { 2 private RealImage image = null; private String filename = null; 3 4 5 public ProxyImage(final String FILENAME) { filename = FILENAME; } 6 7 8 9 public void displayImage() { if (image == null) { image = new RealImage(filename); } image.displayImage(); } 10 11 12 13 14 15 16 } Código 4.8.3: Classe ProxyImage em Java 1 2 3 4 -class(proxyImage). -implements(image). -export([new/1, display_image/0]). -constructor([new/1]). 5 6 attributes. 7 8 9 Image; Filename. 10 11 methods. 12 13 14 new(Filename) -> self::Filename = Filename. 15 16 17 18 19 20 21 22 23 24 25 display_image() -> Temp = self::Image, if (Temp == []) -> self::Image = realImage::new(self::Filename); true -> io:format("") end, Temp2 = self::Image, Temp2::display_image(). Código 4.8.4: Classe ProxyImage em ooErlang A classe ProxyImage, conforme está especificado na utilização do padrão Proxy, controla o acesso e a utilização dos métodos da classe RealImage. Desta forma, sempre que uma Padrão Proxy 111 imagem for instanciada e posteriormente visualizada pelo usuário, a classe ProxyImage é a intermediadora entre a classe principal e a classe real da imagem (RealImage). Isto significa dizer que as chamadas de métodos utilizados na classe RealImage passam primeiro pela classe ProxyImage. A classe ProxyImage possui dois atributos principais, sendo que um deles é o nome do arquivo de imagem a ser instanciado e posteriormente visualizado, e o outro é uma instância da classe RealImage. Esta classe também possui dois métodos, sendo eles um construtor e o método responsável por mostrar a imagem (displayImage()), chamados pela classe principal. Os códigos mostrados em 4.8.5 e 4.8.6 apresentam, respectivamente, as implementações da classe principal ProxyExample. 1 class ProxyExample { 2 public static void main(String[] args) { final Image IMAGE1 = new ProxyImage("HiRes_10MB_Photo1"); final Image IMAGE2 = new ProxyImage("HiRes_10MB_Photo2"); 3 4 5 6 IMAGE1.displayImage(); IMAGE1.displayImage(); IMAGE2.displayImage(); IMAGE2.displayImage(); IMAGE1.displayImage(); 7 8 9 10 11 // // // // // loading loading loading loading loading necessary unnecessary necessary unnecessary unnecessary } 12 13 14 } Código 4.8.5: Classe principal ProxyExample em Java 1 2 -class(proxyExample). -export([main/0]). 3 4 class_methods. 5 6 7 8 main() -> Image1 = proxyImage::new("HiRes_10MB_Photo1"), Image2 = proxyImage::new("HiRes_10MB_Photo2"), 9 10 11 12 13 14 Image1::display_image(), Image1::display_image(), Image2::display_image(), Image2::display_image(), Image1::display_image(). %%loading %%loading %%loading %%loading %%loading necessary unnecessary necessary unnecessary unnecessary Código 4.8.6: Classe principal ProxyExample em ooErlang Padrão Proxy 112 Nesta classe principal inicialmente são instanciados dois objetos que representam dois arquivos de imagem a terem sua visualização simulada por meio da utilização do padrão Proxy. Após as devidas instanciações, o método displayImage(), cujas classes RealImage e ProxyImage implementam, é utilizado seguidamente para verificar o funcionamento do simulador de visualização de imagens. A classe ProxyImage sempre que recebe uma chamada do método displayImage(), verifica se a instância referente à imagem real já foi criada. Em caso negativo, esta classe instancia um novo objeto da classe RealImage, passando por parâmetro o nome do arquivo correspondente à imagem. Se o objeto referente à imagem já tiver sido previamente instanciado, o objeto ProxyImage apenas repassa a responsabilidade de mostrar a imagem ao objeto da classe RealImage. A execução desta classe é mostrada em Java e ooErlang pelas figuras mostradas em 4.20 e 4.21. Figura 4.20: Execução do exemplo para o padrão Proxy em Java. Padrão Proxy 113 Figura 4.21: Execução do exemplo para o padrão Proxy em ooErlang. Na classe RealImage, sempre que um objeto novo for instanciado, antes de ele poder ser visualizado, o mesmo deverá ser carregado em disco. Isto é simulado dentro do construtor da classe RealImage. Neste construtor é realizada a chamada do método loadImageFromDisk(), sendo que, a partir da segunda chamada de visualização de imagens, este carregamento em disco não é mais necessário. Portanto, as imagens a serem testadas devem possuir apenas um carregamento antes das respectivas visualizações. Diversas são as situações em que é possı́vel e até mesmo necessário utilizar o padrão de projetos Proxy. Este padrão de projetos é mais comumente utilizado em sistemas que necessitem ter acesso remoto a outra máquina, e manipular objetos muito complexos. E para facilitar este trabalho, é utilizada uma classe intermediária, que faz o papel de representar de forma simplificada o objeto a ser acessado, reduzindo erros de comunicação na execução do referido sistema. Tratando-se deste exemplo, verificou-se outra forma de se utilizar o padrão Proxy. Neste caso, o usuário possui um sistema visualizador de imagens, e tem por objetivo visualizálas da forma mais eficiente possivel. Porém, sempre que uma imagem é visualizada, ela deve primeiramente ser carregada na memória volátil do computador. Utilizando-se deste Conclusões dos Padrões de Projeto Estruturais em ooErlang 114 padrão, implementou-se um objeto intermediário para tratar das visualizações, assim como o usuário pode verificar quando uma imagem está sendo carregada pela primeira vez. Algumas consequências podem ser verificadas na utilização do padrão Proxy. Em se tratando de sistemas com acesso remoto, uma classe Proxy remota consegue esconder o fato de que o objeto acessado reside em um diferente endereço eletrônico. Em se tratando de um Proxy virtual (caso do exemplo utilizado), é possı́vel realizar otimizações na criação de diversos objetos. Em relação a um Proxy de proteção, verifica-se um melhor controle de segurança ao objeto que está sendo acessado por intermédio da classe Proxy. 4.9 Conclusões dos Padrões de Projeto Estruturais em ooErlang Conforme demonstrado nos exemplos utilizados para implementar os padrões de projeto estruturais, verifica-se que a caracterı́stica principal deste tipo de padrão tem por objetivo básico se preocupar com a forma com que os objetos são compostos para criar estruturas maiores. Um padrão que pode ser citado nesta caracterı́stica é o Adapter, em que é necessário criar uma classe para adaptar interfaces inicialmente incompatı́veis, tornando o sistema mais estruturado e consequentemente maior. Para todas as implementações obtidas em Java, é gerada uma implementação similar em ooErlang, com a mesma quantidade de classes para ambas as abordagens. As caracterı́sticas de sintaxe do ooErlang assim como suas ferramentas que permitem a utilização de elementos da orientação à objetos, permitem a escrita de fontes sintaticamente parecidos, mas não iguais, uma vez que o ooErlang foi desenvolvido com base no Erlang, possuindo as caracterı́sticas próprias desta linguagem funcional. Para todos os sete (7) padrões de projeto estruturais implementados, compilados e testados em ooErlang, o resultado das execuções mostrou-se igual às execuções em Java. Desta forma, os comportamentos esperados em ooErlang são os obtidos, uma vez que não diferiram das implementações que haviam sido anteriormente obtidas em Java. Portanto, para aplicações que necessitem utilizar elementos e caracterı́sticas dos padrões de projeto estruturais, o ooErlang demonstra ser uma ferramenta bastante útil e eficaz. Capı́tulo 5 Padrões de Projeto Comportamentais Neste capı́tulo são estudados os últimos onze (11) padrões de projetos definidos por [Gamma et al., 1994]. Tratam-se dos padrões de projeto comportamentais. A quantidade de padrões comportamentais é maior, pois existem mais situações em que estes padrões podem ser aplicados. Para cada padrão é utilizado um exemplo prático, cuja modelagem em Java e ooErlang é comparada, assim como a sintaxe. Alguns códigos-fonte são apresentados, da mesma forma, a execução para cada padrão de projetos também é mostrada, observando os resultados obtidos com a utilização da extensão ooErlang. 5.1 Conceitos Iniciais Os padrões de projeto comportamentais preocupam-se com algoritmos e com a delegação de responsabilidades entre objetos [Gamma et al., 1994]. Conforme suas caracterı́sticas próprias, os padrões de projetos comportamentais tem foco não somente com as classes ou objetos, mas principalmente, com a comunicação entre esses objetos. Em situações onde é difı́cil verificar o funcionamento de um sistema em tempo de execução, os padrões de projeto comportamentais podem ser utilizados para gerenciar o fluxo de informações entre os objetos. Nas ocasiões em que os padrões de projeto comportamentais são aplicados nas classes de um determinado sistema, geralmente é utilizado o conceito de herança com o obje- Padrão Chain of Responsibility 116 tivo de distribuir um determinado comportamento entre as classes. Esta é uma forma de abordagem que evidencia uma das principais caracterı́sticas dos padrões de projeto comportamentais, que é o foco na forma como os objetos em um determinado sistema estão interconectados. Os conceitos dos padrões de projeto comportamentais ao serem aplicados a objetos, mostram que a herança já não é muito utilizada, em detrimento da composição de objetos. Alguns desses padrões com foco nos objetos, implementam soluções em que um pequeno grupo de objetos interconectados é trabalhado em seu comportamento para realizar uma tarefa na qual apenas um objeto não seria capaz de realizar. Outros padrões de projetos comportamentais preocupam-se em encapsular um comportamento em um objeto e delegar requisições a ele [Gamma et al., 1994]. Todos os códigos-fonte deste capı́tulo podem ser encontrados em [GitHub, 2008]. 5.2 Padrão Chain of Responsibility Analisando-se sua original definição, o padrão de projetos Chain of Responsibility tem por objetivo principal: “Evitar acoplar o remetente de uma requisição ao seu destinatário ao dar a mais de um objeto a chance de servir à requisição. Compõe os objetos em cascata e passa a requisição pela corrente até que um objeto a sirva”. Verifica-se que este padrão pode ser bem utilizado em sistemas que precisem possuir diferentes objetos para tratar de diferentes requisições, com diferentes respostas. Portanto, o padrão de projetos Chain of Responsibility pode ser utilizado em casos onde mais de um objeto pode atender a uma requisição, sendo que este objeto não é especificado. Este padrão também é utilizável quando, por exemplo, seja necessário delegar uma requisição a apenas um de diversos objetos, sem que este objeto seja especificado explicitamente. Outra situação na qual este padrão pode ser utilizado é quando o conjunto de objetos responsável por atender a uma requisição deve ser especificado em tempo de execução. Padrão Chain of Responsibility 5.2.1 117 Exemplo utilizado para este padrão O exemplo utilizado para exemplificar o padrão de projetos Chain of Responsibility foi retirado de [Kulandai, 2012], e trata-se de um sistema que analisa valores numéricos inteiros. Esse sistema recebe como entrada um valor numérico e analisa este valor com relação ao seu sinal. Desta forma, a saı́da do programa corresponde ao tipo de sinal do valor numérico de entrada. Para que este padrão possa ser utilizado, existe a necessidade de se implementar a “corrente de responsabilidades” que caracteriza este padrão. Desta forma, são modeladas as classes NegativeProcessor, PositiveProcessor e ZeroProcessor. Estas são as correspondentes classes que podem processar os valores de entrada. A figura mostrada em 5.1 apresenta com detalhes o diagrama de classes deste exemplo para melhor entendimento. Figura 5.1: Diagrama de classes do exemplo para o padrão Chain of Responsibility. Observa-se que está sendo utilizada uma interface chamada Chain. Esta interface, com maior nı́vel de abstração, é implementada pelas subclasses que compõem a corrente de responsabilidades presente no padrão. Portanto, para que uma requisição seja processada, ela deve ser utilizada ao longo da corrente de responsabilidades. Cada instância das classes NegativeProcessor, PosiviteProcessor e ZeroProcessor, recebe a requisição do usuário e, em Padrão Chain of Responsibility 118 seguida, processa ou passa esta requisição para o elo seguinte da corrente. A classe Number refere-se às requisições de entrada, que são valores numéricos inteiros positivos, negativos ou nulos. 5.2.2 Comparação das implementações em Java e ooErlang A utilização do padrão de projetos Chain of Responsibility e sua aplicação neste exemplo é verificada justamente na utilização das classes que implementam a interface Chain. Estas classes são as que, de fato, realizam o trabalho especificado na definição do padrão que corresponde a uma corrente de responsabilidades. A interface Chain, mostrada nos códigos 5.2.1 e 5.2.2 (Java e ooErlang respectivamente), apresenta as assinaturas de dois métodos implementados nas subclasses que representam a corrente de responsabilidades. 1 public interface Chain { 2 public abstract void setNext(Chain nextInChain); 3 4 public abstract void process(Number request); 5 6 } Código 5.2.1: Interface Chain em Java 1 2 -interface(chain). -export([set_next/1, process/1]). 3 4 methods. 5 6 7 set_next(NextInChain). process(Request). Código 5.2.2: Interface Chain em ooErlang O método da interface Chain são setNext() e process(). O primeiro recebe como parâmetro uma instância das classes que implementam os elos da corrente, podendo ser NegativeProcessor, PositiveProcessor e ZeroProcessor. Este método (setNext()) tem por objetivo o de estruturar a corrente a ser implementada, configurando a ordem dos elos da referida corrente. Já o método process() tem o objetivo de processar determinada requisição em um dos elos da corrente, recebendo como parâmetro de entrada o valor numérico Padrão Chain of Responsibility 119 da requisição. As classes que implementam a interface Chain, possuem uma estrutura de implementação semelhante. A diferença principal entre estas classes está no método process() de cada uma. Os códigos mostrados em 5.2.3 e 5.2.4 mostram as implementações da classe PositiveProcessor em Java e ooErlang, nesta ordem. Verifica-se que esta classe (assim como as outras que implementam a interface Chain), possuem um atributo, chamado nextInChain. Este atributo refere-se ao próximo elo da corrente de responsabilidades. 1 public class PositiveProcessor implements Chain { 2 private Chain nextInChain; 3 4 public void setNext(Chain c) { nextInChain = c; } 5 6 7 8 public void process(Number request) { if (request.getNumber() > 0) { System.out.println("PositiveProcessor : " + request.getNumber()); } else { nextInChain.process(request); } } 9 10 11 12 13 14 15 16 17 } Código 5.2.3: Classe PositiveProcessor em Java O atributo da classe PositiveProcessor, de nome nextInChain, e que também está presente nas outras classes que implementam a interface Chain, é do tipo Chain, portanto, pode ser representado por qualquer instância das subclasses da interface Chain. Este atributo é utilizado na implementação do método setNext(), recebendo como parâmetro de entrada uma instância a ser o próximo elo da corrente responsável por processar as requisições de entrada. No método process() encontra-se a diferença principal das implementações da interface Chain. Em relação à classe PositiveProcessor, este método, ao receber como parâmetro de entrada a requisição a ser processada, faz uma verificação deste valor numérico de entrada. O resultado desta verificação define se esta requisição vai ser processada no elo atual da corrente ou se vai ser passada ao próximo elo. A definição do elo da corrente é mostrada na classe principal chamada TestChain, implementada em Java e ooErlang e apresentada, Padrão Chain of Responsibility 1 2 3 120 -class(positiveProcessor). -implements(chain). -export([set_next/1, process/1]). 4 5 attributes. 6 7 NextInChain. 8 9 methods. 10 11 12 set_next(C) -> self::NextInChain = C. 13 14 15 16 17 18 19 20 21 22 process(Request) -> Number = Request::get_number(), if (Number > 0) -> io:format("PositiveProcessor: ~p~n", [Number]); true -> Next = self::NextInChain, Next::process(Request) end. Código 5.2.4: Classe PositiveProcessor em ooErlang respectivamente, nos códigos 5.2.5 e 5.2.6. 1 2 3 4 5 6 7 8 public class TestChain { public static void main(String[] args) { // configure Chain of Responsibility Chain c1 = new NegativeProcessor(); Chain c2 = new ZeroProcessor(); Chain c3 = new PositiveProcessor(); c1.setNext(c2); c2.setNext(c3); 9 // calling chain of responsibility c1.process(new Number(99)); c1.process(new Number(-30)); c1.process(new Number(0)); c1.process(new Number(100)); 10 11 12 13 14 } 15 16 } Código 5.2.5: Classe principal TestChain em Java Observando-se a implementação realizada na classe principal TestChain, tanto em Java quanto em ooErlang, verifica-se que, inicialmente são instanciados três (3) objetos, pertencentes respectivamente às classes NegativeProcessor, ZeroProcessor e, finalmente, Positi- Padrão Chain of Responsibility 1 2 121 -class(testChain). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 10 11 12 main() -> %%Configure chain of Responsibility C1 = negativeProcessor::new_(), C2 = zeroProcessor::new_(), C3 = positiveProcessor::new_(), C1::set_next(C2), C2::set_next(C3), 13 14 15 16 17 18 %%Calling chain of responsibility C1::process(number::new(99)), C1::process(number::new(-30)), C1::process(number::new(0)), C1::process(number::new(100)). 19 Código 5.2.6: Classe principal TestChain em ooErlang veProcessor. Em seguida a estrutura da corrente de responsabilidades é organizada com a utilização do método setNext. Desta forma, o primeiro elo da corrente vai ser a instância de NegativeProcessor, seguida de ZeroProcessor, finalizando com PositiveProcessor. Após as devidas instanciações e estruturações dos elos da corrente em relação à ordem de processamento das requisições, o passo seguinte é o de realizar as requisições de entrada. Para tal são feitos testes que comprovem se o processamento nos elos está correto. São passados quatro valores numéricos, de tal forma que todos os objetos a tratar das requisições tenham que processá-la ao menos uma vez. Desta forma verifica-se a comprovação da utilização do padrão Chain of Responsibility. As imagens mostradas em 5.2 e 5.3 mostram as execuções das implementações em Java e ooErlang, respectivamente. Verificando-se o resultado das execuções das implementações em Java e ooErlang, observa-se que ambas as abordagens resultaram na mesma saı́da. Desta forma, em ambas as linguagens, todas as requisições foram passadas de elo em elo da corrente de responsabilidades, até que chegassem ao elo que pudesse processar a requisição. Analisando as implementações da classe principal TestChain, percebe-se que sempre as requisições irão inicialmente ser passadas ao primeiro elo da corrente, NegativeProcessor. Ao receber uma requisição, a classe ZeroProcessor, mediante o método process(), verifica se este valor numérico recebido é negativo. Se for, é feito o processamento nesta mesma Padrão Chain of Responsibility 122 Figura 5.2: Execução do exemplo para o padrão Chain of Responsibility em Java. classe. Caso não seja, esta requisição simplesmente é passada para o elo seguinte da corrente por meio do atributo nextInChain. É importante notar que cada elo não tem conhecimento do elo seguinte, apenas repassa a responsabilidade para o próximo objeto processador da corrente. A extensão da linguagem de programação Erlang para suporte à orientação a objetos, ooErlang, demonstrou por meio de uma implementação similar a um exemplo em Java do padrão Chain of Responsibility, resultado semelhante ao encontrado na própria implementação em Java. Este exemplo, conforme a definição deste padrão de projeto, teve como objetivo principal o de desacoplar uma requisição de seu destinatário, abrindo possibilidades para que mais de um destinatário pudesse atender a uma determinada requisição. Na utilização do padrão Chain of Responsibility, ao ser estruturada uma corrente para atender a diversas requisições distintas, é possı́vel fazer com que o último elo da corrente seja Padrão Command 123 Figura 5.3: Execução do exemplo para o padrão Chain of Responsibility em ooErlang. responsável por atender a uma requisição que não se encaixe em nenhum dos elos anteriores da corrente. Neste exemplo simplificado, foi considerado que todas as requisições seriam valores numéricos inteiros (não importando o sinal). Desta forma não existiu a necessidade de utilizar um elo para casos de excessão. Mas é possı́vel e até recomendável utilizar um elo que processe situações não esperadas. 5.3 Padrão Command Verificando-se a definição deste padrão de projeto, o padrão Command tem por objetivo principal: “Encapsular uma requisição como um objeto, permitindo que clientes parametrizem diferentes requisições, filas ou requisições de log, e suportar operações reversı́veis” [Gamma et al., 1994]. Desta forma é possivel utilizar vários comandos diferentes, de forma que exista um desacoplamento entre quem realiza a requisição de uma ação e o objeto que, de fato, realiza a referida ação. Assim é possı́vel realizar um ação sem precisar saber a ação realizada nem o recebedor da ação. Desta forma, o padrão de projetos Command pode ser bem utilizado em situações Padrão Command 124 onde seja necessário parametrizar objetos em relação a ações que precisem ser realizadas. Também pode ser aplicado a sistemas em que seja importante especificar listas de comandos e executá-los em momentos diferentes. Uma outra caracterı́stica importante do padrão Command é a possibilidade de suportar operações reversı́veis, de tal forma que uma ação realizada por último possa ser desfeita, assim como suportar registro de ações, para que seja possı́vel recuperar um estado do sistema se houver uma súbita falha, por exemplo. 5.3.1 Exemplo utilizado para este padrão Para ilustrar a utilização do padrão de projetos Command, é utilizado um exemplo retirado de [Freeman et al., 2004], no qual é implementado um controlador que automatiza diversos dispositivos de uma casa. É um controlador universal programável que pode gerenciar o funcionamento de equipamentos, assim como permitir operações reversı́veis, semelhante ao que é determinado pela definição deste padrão de projeto. Este controle de automação residencial pode ser programado para que cada botão seja configurado a um tipo de ação, de acordo com a necessidade do cliente. Neste exemplo simplificado, as ações do controle estão voltadas para dois principais elementos controláveis: As luzes do ambiente e o ventilador de teto da casa. Mas para cada um desses dispositivos existe uma série de comandos, como por exemplo, ligar luz, desligar luz, etc. Cada diferente comando é modelado como sendo um objeto que passa a encapsular um método chamado execute(), responsável por executar o referido comando. A figura 5.4 mostra com mais detalhes um diagrama de classes para este exemplo. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Verifica-se ao observar a figura 5.4 que nove (9) comandos foram implementados, ou seja, nove classes cujas instâncias devem encapsular uma determinada ação foram modeladas. Dessas nove, quatro (4) comandos são especı́ficos para as luzes da casa e quatro (4) outros destinam-se a controlar o ventilador de teto. O comando restante não realiza nenhuma ação. Todas as classes cujas instâncias devem encapsular um comando estão implementando uma interface em comum, de nı́vel abstrato mais elevado, chamada Command, que possui os métodos para executar a ação e desfazer a última ação realizada. Padrão Command 125 Figura 5.4: Diagrama de classes do exemplo para o padrão Command. 5.3.2 Comparação das implementações em Java e ooErlang Para cada um dos dispositivos comandados pelo controlador modelado no sistema exposto no presente exemplo, é implementada uma classe que armazena os dados referentes aos dispositivos. No caso das luzes da casa, a classe criada se chama Light, e no caso dos ventiladores de teto, chama-se CeilingFan. Assim sendo, cada uma das classes que implementa a interface Command, atua em um dos dispositivos controlados na casa, podendo estar relacionado a uma luz ambiente ou a um dos ventiladores de teto da residência. As classes que realizam comandos especı́ficos para o controle de luzes são LightOnCom- Padrão Command 126 mand, LightOffCommand, DimmerLightOnCommand, DimmerLightOffCommand. Em relação aos comandos que controlam os ventiladores de teto da casa, temos os comandos CeilingFanHighCommand, CeilingFanLowCommand, CeilingFanMediumCommand e CeilingFanOffCommand. O nome de cada classe está relacionado com o comando da mesma. Para as luzes os comandos são de ligar, desligar e controlar a intensidade de luz. Para os ventiladores de teto é possı́vel ajustar para intensidade alta, média, baixa ou desligado. A classe LightOnCommand, implementada em Java e ooErlang está mostrada nos códigos 5.3.1 e 5.3.2 respectivamente. Esta classe possui dois atributos principais, light (referente à classe Light) e level, que armazena o nı́vel de intensidade de luz. Como esta é uma das classes que implementa a interface Command, são implementados os métodos execute() para realizar a execução do comando ligar luz e o método undo que retorna ao nı́vel anterior de intensidade de luz. 1 2 3 4 5 6 public class LightOnCommand implements Command { Light light; int level; public LightOnCommand(Light light) { this.light = light; } 7 public void execute() { level = light.getLevel(); light.on(); } 8 9 10 11 12 public void undo() { light.dim(level); } 13 14 15 16 } Código 5.3.1: Classe LightOnCommand em Java A utilização do método que possibilita operações reversı́veis (undo) é possivel pelo fato de que, sempre ao se utilizar o comando para ligar a luz de determinado ambiente na residência, o nı́vel atual de luz é armazenado no atributo level. Se, porventura, esse nı́vel mudar na utilização de outro comando e, seja necessário retornar ao nı́vel anterior, este nı́vel (que já vai estar armazenado) poderá ser recuperado. Todos os comandos implementados para este exemplo funcionam de forma semelhante. Em relação aos comandos que tratam do ventilador de teto, o comando para desfazer uma operação armazena a última intensidade Padrão Command 1 2 3 4 127 -class(lightOnCommand). -implements(command). -export([new/1, execute/0, undo/0]). -constructor([new/1]). 5 6 attributes. 7 8 9 Light; Level. 10 11 methods. 12 13 14 new(Light) -> self::Light = Light. 15 16 17 18 19 execute() -> Temp = self::Light, self::Level = Temp::get_level(), Temp::on(). 20 21 22 23 undo() -> Temp = self::Light, Temp::dim(self::Level). Código 5.3.2: Classe LightOnCommand em ooErlang de ventilação configurada. A classe que armazena as caracterı́sticas dos dispositivos de luz da residência estão armazenadas na classe Light. É nesta classe que armazena-se o nı́vel atual de luz configurada. Nos objetos que funcionam como comando, o nı́vel de luz utilizado é necessariamente recuperado da classe Light. Sempre que for feita uma nova instância da classe Light, e for utilizado um comando de ligar luz, o nı́vel de intensidade da luz já é inicializado automaticamente. Os códigos mostrados em 5.3.3 e 5.3.4 mostram, respectivamente, as implementações em Java e ooErlang da classe Light Conforme pode ser observado, a classe Light possui um construtor, um método que permite ligar a luz, outro método que permite desligar a luz, assim como um terceiro método que possibilita modificar a intensidade da luz. Estes métodos não são utilizados diretamente pelo usuário, e sim por meio do controle remoto. Este controle remoto pode ser configurado de acordo com os comandos implementados para controle de luz e circulação de ar. E é por meio dos objetos que funcionam como se fossem os próprio comandos que os métodos na classe Light são utilizados. Padrão Command 1 2 3 128 public class Light { String location; int level; 4 public Light(String location) { this.location = location; } 5 6 7 8 public void on() { level = 100; System.out.println("Light is on"); } 9 10 11 12 13 public void off() { level = 0; System.out.println("Light is off"); } 14 15 16 17 18 public void dim(int level) { this.level = level; if (level == 0) { off(); } else { System.out.println("Light is dimmed to " + level + "%"); } } 19 20 21 22 23 24 25 26 27 28 public int getLevel() { return level; } 29 30 31 32 } Código 5.3.3: Classe Light em Java A classe RemoteControlWithUndo é a classe que implementa as caracterı́sticas próprias do controle remoto que passa a possuir os comandos já definidos pelo usuário. A instância de um objeto desta classe vai ser o controle remoto utilizado para, inicialmente configurar onde cada comando é utilizado e, posteriormente, ser a ferramenta que permite executar os comandos nele inseridos. Os códigos mostrados em 5.3.5 e 5.3.6 apresentam os detalhes principais das implementações em Java e ooErlang, nesta ordem, da classe RemoteControlWithUndo. Observa-se que a classe RemoteControlWithUndo possui três (3) atributos principais. Os dois primeiros são duas listas de sete (7) elementos. Cada lista pertence a uma classe de comandos. Uma lista para os comandos que ligam certo dispositivo e outra lista para os comandos que desligam. O terceiro atributo refere-se ao comando undo, ou desfazer. Padrão Command 1 2 3 129 -class(light). -export([new/1, on/0, off/0, dim/1, get_level/0]). -constructor([new/1]). 4 5 attributes. 6 7 8 Location; Level. 9 10 methods. 11 12 13 new(Location) -> self::Location = Location. 14 15 16 17 on() -> self::Level = 100, io:format("Light is on ~n"). 18 19 20 21 off() -> self::Level = 0, io:format("Light is off ~n"). 22 23 24 25 26 27 28 29 30 dim(Level) -> self::Level = Level, if (Level == 0) -> off(); true -> io:format("Light is dimmed to ~p % ~n", [Level]) end. 31 32 33 get_level() -> self::Level. Código 5.3.4: Classe Light em ooErlang O construtor desta classe (RemoteControlWithUndo, não mostrado nas implementações) inicializa todos os “slots” de memória com o comando NoCommand. Isto simula um controle remoto com as configurações de fábrica. O método setCommand da classe RemoteControlWithUndo é abstraı́do dos fontes mostrados em 5.3.5 e 5.3.6, e têm como objetivo, o de configurar cada slot do controle remoto com um comando especı́fico. Os métodos onButtonWasPushed e offButtonWasPushed realizam a execução do comando que foi previamente configurado para o correspondente slot (passado por parâmetro). Se não houver comando configurado, nenhuma ação é realizada, de acordo com a implementação da classe NoCommand. A classe principal, RemoteLoader é a classe utilizada para inicializar e configurar o con- Padrão Command 1 2 3 4 130 public class RemoteControlWithUndo { Command[] onCommands; Command[] offCommands; Command undoCommand; 5 6 7 8 9 public void onButtonWasPushed(int slot) { onCommands[slot].execute(); undoCommand = onCommands[slot]; } 10 11 12 13 14 public void offButtonWasPushed(int slot) { offCommands[slot].execute(); undoCommand = offCommands[slot]; } 15 16 17 18 public void undoButtonWasPushed() { undoCommand.undo(); } Código 5.3.5: Classe RemoteControlWithUndo em Java 1 2 3 4 -class(remoteControlWithUndo). -export([new/0, set_command/3, on_button_was_pushed/1, off_button_was_pushed/1]). -export([undo_button_was_pushed/0, to_string/0]). -constructor([new/0]). 5 6 attributes. 7 8 9 10 OnCommands; OffCommands; UndoCommand. 11 12 methods. 13 14 15 16 17 on_button_was_pushed(Slot) -> Temp = aux_get_element(Slot, self::OnCommands), Temp::execute(), self::UndoCommand = Temp. 18 19 20 21 22 off_button_was_pushed(Slot) -> Temp = aux_get_element(Slot, self::OffCommands), Temp::execute(), self::UndoCommand = Temp. 23 24 25 26 undo_button_was_pushed() -> Temp = self::UndoCommand, Temp::undo(). Código 5.3.6: Classe RemoteControlWithUndo em ooErlang trole remoto, assim como testar a execução deste exemplo. Os códigos mostrados em 5.3.7 Padrão Command 131 e 5.3.8 mostram os detalhes relevantes da implementação da classe principal deste exemplo em Java e ooErlang, respectivamente. 1 public class RemoteLoader { 2 public static void main(String[] args) { RemoteControlWithUndo remoteControl = new RemoteControlWithUndo(); 3 4 5 6 Light livingRoomLight = new Light("Living Room"); 7 8 LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); 9 10 11 12 13 remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff); 14 15 16 remoteControl.onButtonWasPushed(0); remoteControl.offButtonWasPushed(0); System.out.println(remoteControl); remoteControl.undoButtonWasPushed(); remoteControl.offButtonWasPushed(0); remoteControl.onButtonWasPushed(0); System.out.println(remoteControl); remoteControl.undoButtonWasPushed(); 17 18 19 20 21 22 23 24 } 25 26 } Código 5.3.7: Classe principal RemoteLoader em Java O método principal da classe RemoteLoader (main), inicialmente instancia um objeto da classe RemoteControlWithUndo, para ser o objeto representante do controle remoto em si. Depois é instanciado um objeto do tipo Light, para que possa haver controle das luzes de determinado ambiente. Em seguida são instanciados dois objetos que funcionam como comandos de controle das luzes (LightOnCommand e LightOffCommand). Por último estes dois comandos são configurados nas posições iniciais do controle. Após as configurações, os botões são testados, e entre os testes dos botões, também é impresso o status atual do controle remoto, ou seja, quais comados estão configurados em quais botões. O botão responsável por desfazer uma ação (undo) também é testado, para verificar se sua execução ocorre de acordo com o esperado. As figuras mostradas em 5.5 e 5.6 mostram com detalhes o resultado dos testes realizados em Java e ooErlang, nesta sequência. Padrão Command 1 2 132 -class(remoteLoader). -export([main/0]). 3 4 class_methods. 5 6 7 main() -> RemoteControl = remoteControlWithUndo::new(), 8 9 LivingRoomLight = light::new("Living Room"), 10 11 LivingRoomLightOn = lightOnCommand::new(LivingRoomLight), 12 13 LivingRoomLightOff = lightOffCommand::new(LivingRoomLight), 14 15 RemoteControl::set_command(0, LivingRoomLightOn, LivingRoomLightOff), 16 17 18 19 20 21 22 23 24 RemoteControl::on_button_was_pushed(0), RemoteControl::off_button_was_pushed(0), RemoteControl::to_string(), RemoteControl::undo_button_was_pushed(), RemoteControl::off_button_was_pushed(0), RemoteControl::on_button_was_pushed(0), RemoteControl::to_string(), RemoteControl::undo_button_was_pushed(). Código 5.3.8: Classe principal RemoteLoader em ooErlang Verifica-se que, ao serem pressionados os botões relacionados com a ação de ligar e desligar as luzes de determinado ambiente da residência, as mensagens de retorno esperadas são mostradas, comprovando que a execução realizou-se da forma como estava prevista. Assim, de acordo com a definição do padrão de projetos Command, também é possivel verificar quais são os comandos atualmente configurados no controle remoto, em todas as posições válidas, e também na posição referente ao comando de desfazer uma ação (undo). Comprovou-se, utilizando-se de uma aplicação implementada em Java que uma implementação desta mesma aplicação em ooErlang resulta no mesmo comportamento de saı́da. A extensão do Erlang para orientação a objetos permite que sejam utilizados sistemas com as principais caracterı́sticas da utilização do padrão de projetos Command. Isto significa dizer que o ooErlang permite que objetos sejam utilizados para encapsular ações em um sistema, de tal forma que exista um conjunto de objetos funcionando como métodos de comando para determinada situação. No exemplo prático demonstrado, é modelado um sistema de automação residencial. As ações de comando de todo o sistema são implementadas por meio de um controle Padrão Command 133 Figura 5.5: Execução do exemplo para o padrão Command em Java. residencial. Este controle possui dois pares de botões para cada dispositivo da casa, um para ligar e outro para desligar. Além disso, existe um botão geral com o simples objetivo de desfazer uma última ação realizada. Desta forma todas as caracterı́sticas do padrão Command foram de fato implementadas. Verifica-se que, com a utilização do padrão de projetos Command, é possivel desacoplar o objeto que invoca um comando do objeto que realiza a ação especificada. Como cada comando é modelado de forma a ser um objeto, os comandos podem ser manipulados e estendidos como qualquer outro objeto. Em situações onde seja necessário incluir novos comandos, o padrão Command mostra-se bastante versátil e flexı́vel, pois não é necessário modificar nenhum código-fonte já existente para relizar esta ação, sendo necessário apenas criar um novo objeto com esta finalidade. Padrão Interpreter 134 Figura 5.6: Execução do exemplo para o padrão Command em ooErlang. 5.4 Padrão Interpreter O padrão de projetos Interpreter, de acordo com sua principal definição, tem o objetivo de: “Dada uma linguagem, definir uma representação para sua gramática junto com um interpretador que usa a representação para interpretar sentenças na linguagem” [Gamma et al., 1994]. Este padrão pode ser bem utilizado quando seja necessário criar gramáticas de linguagens de programação com uma quantidade de regras limitada, assim como quando seja possı́vel e até mesmo necessário modelar um problema como uma gramática simplificada. O padrão de projetos Interpreter é possı́vel de ser utilizado quando existe a necessidade de interpretar uma linguagem e, ao mesmo tempo, seja possı́vel representar as declarações de determinada gramática por meio de árvores sintáticas. É mais recomendável utilizar o padrão Interpreter em situações onde a gramática é simples, pois para uma gramática muito extensa, sua hierarquia de classes torna-se grande e de difı́cil gerenciamento. Na utilização deste padrão, é importante ressaltar que a eficiência de execução não é o ponto principal, e sim a solução de determinado problema. Padrão Interpreter 5.4.1 135 Exemplo utilizado para este padrão Para melhor compreensão da utilização do padrão Interpreter, é utilizado um exemplo retirado de [Books, 2013] que aplica o conceito deste padrão de projeto. É implementado um sistema simplificado que interpreta a gramática de uma linguagem responsável por calcular problemas de aritmética simples. Trata-se de uma gramática para uma linguagem em que é possivel utilizar variáveis e cálculos que envolvam soma e subtração de valores inteiros. Desta forma, para este exemplo é modelada uma classe para cada regra da gramática desenvolvida. Em se tratando de cálculos que envolvam o operador de subtração, utiliza-se a classe Minus. Para cálculos que possuam elementos de soma, foi também utilizada uma classe, chamada Plus. Cada valor numérico desta gramática é armazenado em variáveis literais, e para cada uma dessas variáveis é modelada uma classe chamada Variables. Para melhor entendimento das relações entre as classes, a figura 5.7 mostra um diagrama de classes deste exemplo. Observa-se, por meio da figura 5.7, que é utilizada uma interface chamada Expression. Esta interface possui a abstração do método interpret(), tratando-se do método principal deste padrão. Cada uma das classes que representa uma regra nesta gramática implementa o método interpret() que possui as caracterı́sticas próprias de sua regra especı́fica. Também é possivel observar a classe Number, que tem por objetivo passar valores numéricos para as variáveis da expressão. 5.4.2 Comparação das implementações em Java e ooErlang As classes Plus, Minus, Variable e Number possuem caracterı́sticas semelhantes. A diferença principal são os atributos das mesmas. No caso das classes Variable e Number, existe apenas um atributo, referente à variável ou a um valor numérico de uma variável. Já em relação às classes Plus e Minus, existem dois atributos que referem-se aos dois operandos da operação que estas classes realizam, o operando da esquerda e o operando da direita. Para melhor entendimento, os códigos mostrados em 5.4.1 e 5.4.2 mostram, respectivamente, as implementações em Java e ooErlang da classe Plus. Verifica-se que, assim como todas as classes que implementam a interface Expression, a Padrão Interpreter 136 Figura 5.7: Diagrama de classes do exemplo para o padrão Interpreter. 1 2 3 4 5 6 7 public class Plus implements Expression { Expression leftOperand; Expression rightOperand; public Plus(Expression left, Expression right) { leftOperand = left; rightOperand = right; } 8 public int interpret(Map<String,Expression> variables) return leftOperand.interpret(variables) + rightOperand.interpret(variables); } 9 10 11 12 13 { } Código 5.4.1: Classe Plus em Java classe Plus possui seus atributos, um construtor e a implementação do método interpret(). Este método recebe como parâmetro de entrada uma tabela com as variáveis e seus respectivos valores. Em Java este parâmetro é implementado por meio de uma tabela hash e Padrão Interpreter 1 2 3 4 137 -class(plus). -implements(expression). -export([new/2, interpret/1]). -constructor([new/2]). 5 6 attributes. 7 8 9 LeftOperand; RightOperand. 10 11 methods. 12 13 14 15 new(Left, Right) -> self::LeftOperand = Left, self::RightOperand = Right. 16 17 18 19 20 21 interpret(Variables) -> Left = self::LeftOperand, Right = self::RightOperand, Right::interpret(Variables) + Left::interpret(Variables). Código 5.4.2: Classe Plus em ooErlang em ooErlang é utilizado um dicionário que funciona similarmente a uma tabela hash. Também é possivel verificar que o método interpret(), implementado na classe Plus, é modelado utilizando-se de recursão, ou seja, ele se chama novamente para o operador esquerdo e direito, com o detalhe de que o valor retornado é a operação de soma entre as chamadas deste método para ambos os operadores. Isto é feito desta forma pois um operador pode tanto ser uma variável como uma outra operação. Neste ponto que verificase o uso de uma árvore sintática para estruturar a sentença. A classe Minus também é utilizada de forma recursiva na implementação do método interpret(). A razão é a mesma pela qual isto é realizado na classe Plus. Já nas classes Variable e Number isto é diferente. Na classe Variable o método interpret() é utilizado apenas para recuperar o valor de uma variável. Na classe Number, este método apenas retorna o valor numérico da classe. A classe Evaluator realiza a construção da árvore sintática da expressão de entrada. Os códigos 5.4.3 e 5.4.4 mostram a implementação da classe Evaluator em Java e ooErlang, respectivamente. Nas implementações mostradas em 5.4.3 e 5.4.4, foi dado ênfase apenas no método principal, evaluator(). Este método recebe como entrada a expressão a ser calculada. Em seguida cada token da expressão é lido e é armazenado em uma estrutura de pilha. Quando Padrão Interpreter 1 2 3 138 public class Evaluator implements Expression { private Expression syntaxTree; Stack<Expression> expressionStack = new Stack<Expression>(); 4 5 6 7 8 9 10 11 12 13 14 public Evaluator(String expression) { for (String token : expression.split(" ")) { if (token.equals("+")) { Expression subExpression = new Plus(expressionStack.pop(), expressionStack.pop()); expressionStack.push(subExpression); } else if (token.equals("-")) { Expression right = expressionStack.pop(); Expression left = expressionStack.pop(); 15 Expression subExpression = new Minus(left, right); expressionStack.push(subExpression); 16 17 } else 18 expressionStack.push(new Variable(token)); } syntaxTree = expressionStack.pop(); 19 20 21 22 } Código 5.4.3: Classe Evaluator em Java um token representando uma soma é encontrado, são retirados os dois elementos do topo da pilha e com eles é criada uma nova regra para calcular a soma, representado por apenas um objeto do tipo Plus. Este objeto é inserido na pilha e a leitura da expressão continua. Ao ser encontrado um token que represente uma subtração, é realizado um procedimento semelhante ao utilizado quando se encontra um token do tipo soma. Dessa forma, a pilha vai sendo estruturada e no final vai haver apenas um objeto cujos atributos são outros objetos e/ou valores numéricos. Desta forma é que a árvore sintática vai sendo construı́da. E é por esse motivo que, quando uma sentença que já possui sua árvore sintática construı́da é interpretada, o método interpret() é utilizado recursivamente. Existem objetos sendo atributos de outros objetos. Os objetos do tipo soma (Plus) e subtração (Minus) possuem necessariamente dois objetos filhos, que são, respectivamente, seus dois atributos. Já o objeto Variable possui apenas um filho, que é um objeto do tipo Number. O objeto do tipo Number então armazena o valor numérico referente à variável do objeto pai. É importante notar que, a gramática para este exemplo, requer uma notação do tipo pós-fixada, representada por valores numéricos seguidos pelo operador dos mesmos. Padrão Interpreter 1 2 3 4 139 -class(evaluator). -implements(expression). -export([new/1, interpret/1]). -constructor([new/1]). 5 6 attributes. 7 8 9 SyntaxTree; ExpressionStack. 10 11 methods. 12 13 14 15 16 new(Expression) -> self::ExpressionStack = [], aux_evaluator(Expression), self::SyntaxTree = aux_pop(). 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 aux_evaluator([]) -> ok; aux_evaluator(Expression) -> Token = lists:nth(1, Expression), RestExpression = lists:nthtail(1, Expression), if (Token == 32) -> aux_evaluator(RestExpression); (Token == 43) -> SubExpression = plus::new(aux_pop(), aux_pop()), aux_push(SubExpression), aux_evaluator(RestExpression); (Token == 45) -> Right = aux_pop(), Left = aux_pop(), SubExpression = minus::new(Left, Right), aux_push(SubExpression), aux_evaluator(RestExpression); true -> Token2 = aux_integer_to_char(Token), aux_push(variable::new(Token2)), aux_evaluator(RestExpression) end. Código 5.4.4: Classe Evaluator em ooErlang A classe principal deste exemplo chama-se InterpreterExample e é utilizada para testar o cálculo de uma expressão escrita na forma pós-fixada, com variáveis as quais são atribuı́dos valores numéricos. Os códigos mostrados em 5.4.5 e 5.4.6 mostram, nesta ordem, as implementações em Java e ooErlang da classe principal IntepreterExample, onde é possivel testar o funcionamento da modelagem e analisar o resultado obtido. No método principal (main) da classe InterpreterExample, inicialmente é declarada a expressão a ser interpretada e calculada. Em seguida, é instanciado um objeto da classe Padrão Interpreter 1 2 3 4 5 6 7 8 9 10 11 12 13 140 public class InterpreterExample { public static void main(String[] args) { String expression = "w x z - +"; Evaluator sentence = new Evaluator(expression); Map<String, Expression> variables = new HashMap<String, Expression>(); variables.put("w", new Number(5)); variables.put("x", new Number(10)); variables.put("z", new Number(42)); int result = sentence.interpret(variables); System.out.println("Result of sentence is: " + result); } } Código 5.4.5: Classe principal InterpreterExample em Java 1 2 -class(interpreterExample). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 main() -> Expression = "w x z - +", Sentence = evaluator::new(Expression), Variables = orddict:new(), 10 11 12 13 Variables2 = orddict:store(w, number::new(5), Variables), Variables3 = orddict:store(x, number::new(10), Variables2), Variables4 = orddict:store(z, number::new(42), Variables3), 14 15 16 Result = Sentence::interpret(Variables4), io:format("Result of sentence is: ~p~n", [Result]). Código 5.4.6: Classe principal InterpreterExample em ooErlang Evaluator, com o objetivo de receber a expressão inicial e analisar token a token dessa expressão, criando assim a árvore sintática dessa expressão. Se a sintaxe da gramática estiver correta na expressão, o resultado final do tratamento com a classe Evaluator vai ser apenas um objeto que irá possuir dentro de si (em seus atributos) todos os outros objetos para cada regra da gramática. Isto é realizado de forma recursiva. Para que a expressão utilizada possa ser calculada, é necessário que as variáveis possuam valores numéricos. Então, após ser criada a árvore sintática da expressão, é criada a tabela de valores para cada uma das variáveis presentes na expressão. Dessa forma, quando a árvore sintática da expressão é interpretada, cada variável vai poder ser substituı́da por seu respectivo valor numérico. Padrão Interpreter 141 Após a criação da expressão, estruturação da árvore sintática da mesma expressão e definição dos valores numéricos para cada variável presente na expressão, é utilizado o método interpret(), que recebe como parâmetro de entrada o objeto-pai resultante da utilização do construtor da classe Evaluator, responsável por criar a árvore sintática. O método interpret() funciona recursivamente, para todos os objetos que estiverem presentes nos atributos de outros objetos, finalizando quando encontram os valores numéricos das variáveis. As figuras 5.8 e 5.9 mostram a execução em Java e ooErlang desta classe. Figura 5.8: Execução do exemplo para o padrão Interpreter em Java. A expressão utilizada para testar o funcionamento deste exemplo é a seguinte: “w x z +”. Ou seja, ao ler esta expressão, as variáveis w, x e z são armazenadas na pilha. Ao ler o token “-”, os valores z e x são desempilhados e é criado um objeto do tipo “x - z” e este objeto é inserido na pilha. Em seguida é lido o token “+” e são novamente retirados dois elementos da pilha, “x - z” e “w”. Assim cria-se o objeto-pai do tipo soma representado por Padrão Interpreter 142 Figura 5.9: Execução do exemplo para o padrão Interpreter em ooErlang. “w + ( x - z )”. Com a atribuição de valores para cada variável desta expressão, é possı́vel calcular o resultado final e mostrá-lo ao usuário. A utilização de gramáticas para resolução de problemas gerais e para criação de linguagens simples, com poucas regras é o principal foco na implementação do padrão Interpreter na modelagem de um sistema. Verifica-se que, tanto em Java quanto em ooErlang, o exemplo estudado, que se utiliza dos conceitos do padrão de projetos Interpreter mostrou funcionar de forma semelhante. Assim, pode-se observar que a extensão ooErlang oferece suporte necessário para utilização deste padrão de projeto. Ao analisar a utilização e as caracterı́sticas do padrão de projetos Interpreter, observase que, independente da gramática modelada, é fácil realizar modificações, assim como extensões. É possı́vel realizar isto por meio de heranças, da mesma forma como novas expressões podem ser obtidas incrementando-se as já existentes. Em contrapartida, quando a gramática torna-se extensa demais, a manutenção do sistema torna-se mais complexa. Isto ocorre pois para cada nova regra de uma gramática, deve existir uma nova classe. Quando uma gramática é muito complexa, é mais apropriado utilizar outras técnicas, como geradores de parser e compiladores [Gamma et al., 1994]. Padrão Iterator 5.5 143 Padrão Iterator O padrão de projetos Iterator, de acordo com sua definição, possui o objetivo de: “Prover uma maneira de acessar os elementos de um objeto agregado sequencialmente sem expor sua representação interna” [Gamma et al., 1994]. Este padrão visa uma melhor forma de interação com um conjunto de objetos, geralmente dispostos em um arranjo, sem que o usuário precise saber de que forma esses objetos são armazenados. Desta forma, se por acaso vários arranjos de objetos são estruturados de forma diferente, o usuário tem a impressão de que o acesso a esses objetos é semelhante. A utilização do padrão de projetos Iterator é recomendada em situações onde seja necessário acessar objetos agregados sem que a estrutura interna dos mesmos fique exposta ao usuário. De uma forma geral, este padrão é utilizado para suportar múltiplas formas na qual seja possı́vel percorrer objetos agregados. Uma das situações em que isto pode ser utilizado é quando, por exemplo, tem-se diferentes estruturas de objetos agregados e necessita-se de uma interface uniforme para percorrer essas diferentes estruturas. Assim, para o usuário, não haverá diferenças entre as estruturas de objetos agregados. 5.5.1 Exemplo utilizado para este padrão Para ilustrar a utilização do padrão de projetos Iterator, é utilizado um exemplo retirado de [Freeman et al., 2004], em que existe o seguinte cenário: Duas lojas de comida pretendem fusionar-se, criando apenas uma loja com o cardápio de ambas. Uma loja vende apenas refeições do café da manhã e chama-se Pancake House e a outra está focada em vender refeições para o almoço e jantar, e chama-se Diner House. Cada uma das duas lojas de comida possui seu próprio cardápio. Ambos os restaurantes possuem um sistema que gerencia os produtos, inclusive seu cardápio. Cada uma utiliza uma classe para armazenar os tipos de pratos próprios. O restaurante de café da manhã possui a classe PancakeHouseMenu e o restaurante de almoços e jantares possui a classe DinerHouseMenu. Porém a estrutura na qual os dois restaurantes armazenam seus produtos é diferente. O restaurante Pancake House armazena seus produtos em uma estrutura chamada “ArrayList” (uma lista em Java que pode ser incrementada), e o restaurante Diner House armazena em um “Array” (lista com tamanho definido). Padrão Iterator 144 Dessa forma, as estruturas que armazenam os cardápios de ambos os restaurantes são distintas e, por consequência, o acesso aos elementos de cada um dos cardápios também é distinto. Portanto, para que os restaurantes possam se unir, e para que haja facilidade no acesso dos ı́tens de ambos os cardápios, é necessário criar uma interface única que possa percorrer ambos os menus sem dificuldade. Para que esta interface de acesso facilitado possa ser utilizada, é necessário aplicar o padrão Iterator neste cenário. Ao utilizar o padrão Iterator, é necessário, criar uma classe de interação para ambos os cardápios. Dessa forma, criaram-se as classes DinerMenuIterator e PancakeHouseMenuIterator. Ambas as classes implementam a interface Iterator. E esta interface possui apenas dois métodos, um para verificar se ainda existe ı́tem e o outro para acessar o ı́tem seguinte do cardápio. Cada uma das classes menu implementa uma interface chamada Menu e foi utilizada uma classe chamada Waiter para servir de acesso entre os cardápios e suas classes “Iterator”. A figura 5.10 mostra um diagrama de classes simplificado deste cenário. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Cada classe do tipo Iterator trabalha com a estrutura especı́fica dos cardápios. Para o usuário final, a impressão vai ser a de que os ı́tens estão armazenados em uma estrutura similar e que são acessados da mesma forma. O padrão Iterator permite que diferentes estruturas com objetos agregados possam ser acessadas de forma semelhante apesar de, internamente, possuı́rem formas de acesso distintas. 5.5.2 Comparação das implementações em Java e ooErlang A classe MenuItem, também mostrada na figura 5.10, refere-se aos ı́tens que são armazenados no cardápio do restaurante Diner House, ou seja, em uma instância da classe DinerMenu. Essa é outra diferença em relação à classe PancakeHouseMenu, que armazena os ı́tens sem instanciar objetos de nenhuma outra classe, pois utiliza uma estrutura chamada Arraylist em Java. Em 5.5.1 e 5.5.2 são mostrados detalhes principais dos códigos em Java e ooErlang da classe DinerMenu. Conforme pode ser observado, a classe DinerMenu armazena seus ı́tens (tipos de comida) em uma arranjo que possui, como caracterı́stica principal, uma quantidade limitada de elementos. Outro ponto importante a ser observado é que todos os elementos presentes Padrão Iterator 145 Figura 5.10: Diagrama de classes do exemplo para o padrão Iterator. na lista de objetos são instâncias da classe MenuItem. Em ooErlang a quantidade de ı́tens presentes na lista também é limitada e é controlada (assim como em Java) pelo atributo max itens, representando a quantidade máxima de ı́tens no cardápio deste restaurante. Ao verificar a implementação da classe PancakeHouseMenu, observa-se que outra estrutura foi utilizada para armazenar os ı́tens. É uma estrutura de arranjo, mas que não precisa necessariamente de instâncias de outra classe para que armazene os objetos do cardápio. Percebe-se, assim, que o arranjo utilizado em PancakeHouseMenu é do tipo “ArrayList”. Assim este arranjo não tem inicialmente uma quantidade limitada de elementos a ser inserida. Os códigos mostrados em 5.5.3 e 5.5.4 apresentam os principais detalhes da implementação da classe PancakeHouseMenu em Java e ooErlang. Padrão Iterator 1 2 3 4 146 public class DinerMenu implements Menu { static final int MAX_ITEMS = 6; int numberOfItems = 0; MenuItem[] menuItems; 5 public DinerMenu() { menuItems = new MenuItem[MAX_ITEMS]; 6 7 8 addItem("Vegetarian BLT", "(Fakin’) Bacon with lettuce & tomato on whole wheat", true, 2.99); addItem("BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99); //outros itens... 9 10 11 12 13 14 15 } 16 17 public Iterator createIterator() { return new DinerMenuIterator(menuItems); } 18 19 20 21 } Código 5.5.1: Classe DinerMenu em Java Verifica-se que, diferentemente das implementações em Java das classes DinerMenu e PancakeHouseMenu, as implementações em ooErlang utilizam ambas a mesma estrutura de armazenamento dos ı́tens do cardápio. E essa estrutura é uma lista simples, que é largamente utilizada em funções do Erlang. Mas os dois cardápios não são semelhantes em ooErlang por dois motivos: O primeiro é que as listas possuem limites distintos de tamanho e o segundo é que uma lista armazena instâncias de outra classe MenuItem e a outra lista insere os elementos diretamente na lista sem instanciar outra classe. Um ponto importante a ser observado nas implementações das classes DinerMenu e PancakeHouseMenu é a utilização do método createIterator(). Neste método é realizada a instância do objeto que irá interagir com todos os ı́tens dos referidos cardápios. Estes objetos levam em consideração a estrutura na qual os ı́tens do cardápio estão armazenadas, possibilitando acessar sequencialmente os referidos ı́tens. Os códigos mostrados em 5.5.5 e 5.5.6 apresentam as implementações em Java e ooErlang da classe DinerMenuIterator. A implementação em Java da classe DinerMenuIterator mostra que, o acesso aos ı́tens é feito diretamente por meio da indexação da posição de cada ı́tem. Sempre que se deseja verificar se existe um novo ı́tem, deve-se avaliar para saber se já chegou-se ao número máximo de ı́tens permitido pela classe DinerMenu. Assim consegue-se implementar, em Padrão Iterator 1 2 3 4 147 -class(dinerMenu). -implements(menu). -export([new/0, add_item/4, get_menu_items/0, create_iterator/0]). -constructor([new/0]). 5 6 attributes. 7 8 9 10 MaxItems; NumberOfItems; MenuItems. 11 12 methods. 13 14 15 16 17 18 19 20 21 22 new() -> self::MenuItems = [], add_item("Vegetarian BLT", "(Fakin’) Bacon with lettuce & tomato on whole wheat", true, 2.99), add_item("BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99). %outros itens... 23 24 25 26 create_iterator() -> NewIterator = dinerMenuIterator::new(self::MenuItems), NewIterator. Código 5.5.2: Classe DinerMenu em ooErlang DinerMenuIterator os dois métodos principais para que seja possı́vel percorrer os ı́tens do cardápio do restaurante: next(), para acessar o elemento seguinte ao atual e hasNext() para verificar se ainda existem elementos não visitados. Pode ser verificado que, na implementação da classe DinerMenuIterator, tanto em Java quanto em ooErlang, cada elemento pertencente ao cardápio de uma instância da classe DinerMenu é um objeto da classe MenuItem. Isto não é o mesmo que ocorre na implementação da classe PancakeHouseMenu, onde cada elemento do cardápio é inserido diretamente em um “ArrayList” na implementação em Java. Em ooErlang a inserção dos elementos ocorre em uma lista semelhante à implementada no código mostrado em 5.5.2, com a diferença de que não é utilizada a classe MenuItem para instanciar os objetos. Ambas as classes DinerMenuIterator e PancakeHouseMenuIterator são utilizadas para percorrer todos os elementos pertencentes às instâncias das respectivas classes DinerMenu e PancakeHouseMenu. A interface Iterator é a implementada pelas classes responsáveis pelo acesso dos respectivos elementos. A classe que se utiliza da interface Iterator é a Padrão Iterator 1 2 148 public class PancakeHouseMenu implements Menu { ArrayList menuItems; 3 public PancakeHouseMenu() { menuItems = new ArrayList(); 4 5 6 addItem("K&B’s Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99); 7 8 9 10 11 addItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99); 12 13 14 15 16 } 17 18 public Iterator createIterator() { return new PancakeHouseMenuIterator(menuItems); } 19 20 21 22 } Código 5.5.3: Classe PancakeHouseMenu em Java 1 2 3 4 -class(pancakeHouseMenu). -implements(menu). -export([new/0, add_item/4, get_menu_items/0, create_iterator/0, to_string/0]). -constructor([new/0]). 5 6 attributes. 7 8 MenuItems. 9 10 methods. 11 12 13 14 15 16 17 18 19 20 new() -> self::MenuItems = [], add_item("K & B’s Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99), add_item("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99). %outros itens... 21 22 23 24 create_iterator() -> NewIterator = pancakeHouseMenuIterator::new(self::MenuItems), NewIterator. Código 5.5.4: Classe PancakeHouseMenu em ooErlang Padrão Iterator 1 2 3 149 public class DinerMenuIterator implements Iterator { MenuItem[] items; int position = 0; 4 public DinerMenuIterator(MenuItem[] items) { this.items = items; } 5 6 7 8 public Object next() { MenuItem menuItem = items[position]; position = position + 1; return menuItem; } 9 10 11 12 13 14 public boolean hasNext() { if (position >= items.length || items[position] == null) { return false; } else { return true; } } 15 16 17 18 19 20 21 22 } Código 5.5.5: Classe DinerMenuIterator em Java classe Waitress, sendo de sua responsabilidade acessar ambos os cardápios utilizando-se da mesma interface. Os códigos mostrados em 5.5.7 e 5.5.8 apresentam, respectivamente em Java e ooErlang, os detalhes principais da implementação da classe Waitress. A classe Waitress possui dois atributos, pancakeHouseMenu e dinerMenu. Estes atributos recebem as instâncias das classes PancakeHouseMenuIterator e DinerMenuIterator respectivamente. Dessa forma é possivel manusear os dois cardápios, e utilizar-se da mesma interface para percorrer os ı́tens dos mesmos. Além de um construtor, a classe Waitress possui também o método printMenu(), no qual os atributos da classe são utilizados para que seja possı́vel percorrer e imprimir todos os elementos de ambos os cardápios. A classe principal deste exemplo chama-se MenuTestDrive. Conforme é mostrado nos códigos 5.5.9 e 5.5.10 que representam as implementações em Java e ooErlang, nesta ordem, da classe principal, são testadas as duas classes que implementam a interface Iterator. Para isto, instanciam-se dois objetos, referentes às classes PancakeHouseMenu e DinerMenu. Em seguida estes objetos são passados por parâmetro na instanciação do objeto da classe Waitress, criando assim os respectivos objetos de interação. Conforme mostrado nas implementações da classe MenuTestDrive, ao ser instanciado Padrão Iterator 1 2 3 4 150 -class(dinerMenuIterator). -implements(iterator). -export([new/1, next/0, has_next/0]). -constructor([new/1]). 5 6 attributes. 7 8 9 Items; Position. 10 11 methods. 12 13 14 new(Items) -> self::Items = Items. 15 16 17 18 19 next() -> MenuItem = lists:nth(self::Position, self::Items), self::Position = self::Position + 1, MenuItem. 20 21 22 23 24 25 26 27 28 29 has_next() -> Pos = self::Position, Itm = self::Items, if (Pos >= length(Itm)) -> false; true -> true end. Código 5.5.6: Classe DinerMenuIterator em ooErlang um objeto da classe Waitress, os parâmetros recebidos na instanciação são instâncias das classes que possuem os diferentes cardápios de produtos. Quando a instância da classe Waitress invoca o método printMenu(), na classe Waitress, internamente, são criadas as instâncias das classes que implementam a interface Iterator e que são responsáveis por percorrer ambas as listas de produtos. Assim, com a chamada de apenas um método foi possı́vel verificar os elementos de duas diferentes agregações. As figuras mostradas em 5.11 e 5.12 mostram a execução do exemplo que implementa o padrão de projetos Iterator em Java e ooErlang, respectivamente. Verifica-se que, ambas as implementações utilizaram os mesmos ı́tens de cardápio, para que a comparação das execuções possa demonstrar semelhança nas implementações realizadas em Java e ooErlang. São percorridas as listas de produtos dos cardápios das classe PancakeHouseMenu e DinerMenu, nesta sequência, imprimindo cada ı́tem de cada cardápio. Padrão Iterator 1 2 3 151 public class Waitress { PancakeHouseMenu pancakeHouseMenu; DinerMenu dinerMenu; 4 public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) { this.pancakeHouseMenu = pancakeHouseMenu; this.dinerMenu = dinerMenu; } 5 6 7 8 9 public void printMenu() { Iterator pancakeIterator = pancakeHouseMenu.createIterator(); Iterator dinerIterator = dinerMenu.createIterator(); 10 11 12 13 System.out.println("MENU\n----\nBREAKFAST"); printMenu(pancakeIterator); System.out.println("\nLUNCH"); printMenu(dinerIterator); 14 15 16 17 } 18 19 private void printMenu(Iterator iterator) { while (iterator.hasNext()) { MenuItem menuItem = (MenuItem)iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.print(menuItem.getPrice() + " -- "); System.out.println(menuItem.getDescription()); } } 20 21 22 23 24 25 26 27 Código 5.5.7: Classe Waitress em Java No exemplo mostrado, o padrão de projetos Iterator é utilizado no caso especı́fico em que há a necessidade de acesso a diferentes conjuntos de objetos (agregações) utilizando-se de uma mesma interface. Desta forma, para o usuário, os acessos realizados nos diferentes conjuntos de objetos aparentam ter sido executados de forma semelhante por terem utilizado uma interface em comum, entretanto, estes objetos estão armazenados em diferentes estruturas, e seu acesso é, logicamente, diferente. Para que seja possı́vel utilizar uma mesma interface com o objetivo de acessar conjuntos de objetos diferentes de forma semelhante, são modeladas classes que implementam esta interface e, internamente, tratam do acesso especı́fico de cada estrutura de armazenamento. O usuário, ao se utilizar de um sistema que possui esta modelagem não irá precisar se preocupar com a estrutura interna de cada agregação de objetos a ser percorrida pelo sistema. Este exemplo ilustra uma situação em que é necessário associar dois sistemas seme- Padrão Iterator 1 2 3 4 5 152 -class(waitress). -export([new/2, print_menu/0, print_menu/1]). -export([print_vegetarian_menu/0, is_item_vegetarian/1]). -export([print_vegetarian_menu/1, is_vegetarian/2]). -constructor([new/2]). 6 7 attributes. 8 9 10 PancakeHouseMenu; DinerMenu. 11 12 methods. 13 14 15 16 new(PancakeHouseMenu,DinerMenu) -> self::PancakeHouseMenu = PancakeHouseMenu, self::DinerMenu = DinerMenu. 17 18 19 20 print_menu() -> Pancake = self::PancakeHouseMenu, Diner = self::DinerMenu, 21 22 23 PancakeIterator = Pancake::create_iterator(), DinerIterator = Diner::create_iterator(), 24 25 26 27 28 io:format("MENU~n----~nBREAKFAST~n"), print_the_menu(Pancake::MenuItems), io:format("~nLUNCH~n"), print_the_menu(Diner::MenuItems). Código 5.5.8: Classe Waitress em ooErlang 1 2 3 4 public class MenuTestDrive { public static void main(String args[]) { PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu(); DinerMenu dinerMenu = new DinerMenu(); 5 Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu); 6 7 waitress.printMenu(); 8 9 } Código 5.5.9: Classe principal MenuTestDrive em Java lhantes porém com estruturas diferentes em um único sistema. Neste caso, a utilização do padrão Iterator tornou possivel a referida associação sem que fosse necessário modificar as estruturas que armazenam os ı́tens de cardápio de cada sistema. Obtém-se, dessa forma, uma solução que não produz muitos impactos na modelagem como um todo e que facilita a manutenção e extensão do referido sistema. Padrão Iterator 1 2 153 -class(menuTestDrive). -export([main/0]). 3 4 class_methods. 5 6 7 8 main() -> PancakeHouseMenu = pancakeHouseMenu::new(), DinerMenu = dinerMenu::new(), 9 10 11 12 io:format("PancakeHouseMenu: ~p~n", [PancakeHouseMenu]), io:format("DinerMenu: ~p~n", [DinerMenu]), Waitress = waitress::new(PancakeHouseMenu, DinerMenu), 13 14 Waitress::print_menu(). Código 5.5.10: Classe principal MenuTestDrive em ooErlang Figura 5.11: Execução do exemplo para o padrão Iterator em Java. Ao se utilizar do padrão de projetos Iterator, percebe-se que uma das caracterı́sticas é a de que este padrão suporta variações no percorrimento de uma agregação, sendo possı́vel Padrão Mediator 154 Figura 5.12: Execução do exemplo para o padrão Iterator em ooErlang. modificar a forma com a qual o conjunto de elementos é visitado. Este padrão também simplifica a interface da agregação. Para o exemplo utilizado, torna-se mais simples incluir novos cardápios com diferentes estruturas, caso isto se mostre necessário, diminuindo problemas em uma possivel extensão do sistema modelado. 5.6 Padrão Mediator Este padrão de projeto, de acordo com sua definição, tem por objetivo: “Definir um objeto que encapsula como um conjunto de objetos interage. Mediator promove acoplamento fraco ao manter objetos que não se referem um ao outro explicitamente, permitindo variar sua interação independentemente” [Gamma et al., 1994]. Pode-se utilizar este padrão de projetos quando é necessário a um conjunto de classes possuir comunicação entre qualquer elemento deste conjunto, e que também seja relativamente fácil inserir ou retirar elementos deste conjunto. O padrão de projetos Mediator é bem utilizado em situações em que exista um conjunto de objetos cuja estrutura de comunicação possui um nı́vel de complexidade bastante Padrão Mediator 155 elevado, resultando em dificuldades no entendimento, na extensão e principalmente na manutenção do sistema. Este tipo de situação pode inclusive se agravar quando torna-se necessária a utilização de subclasses, refinando ainda mais o comportamento do sistema. A utilização do padrão Mediator simplifica as comunicações entre dois ou mais objetos, desacoplando-os e facilitando futuras modificações nos mesmos. 5.6.1 Exemplo utilizado para este padrão O exemplo utilizado que demonstra a utilização de uma mediador para facilitar a comunicação entre diferentes classes foi retirado de [Kulandai, 2012]. Trata-se da modelagem de um sistema de aeroporto simplificado, no qual é possı́vel verificar se um avião está ou não apto a aterrisar na pista de pouso. Desta forma, duas classes principais são utilizadas: Flight, que representa o vôo do avião, e Runway, representando a pista de pouso do aeroporto. Desta forma, apesar destas duas classes não estarem diretamente relacionadas, é necessário que haja comunicação entre ambas para que a ação de pouso de um avião possa ser realizada. Um vôo, ao estar perto de pousar, deve pedir permissão para realizar esta ação, e esta permissão será concedida somente se a pista estiver em condições de receber um pouso. Assim é necessário utilizar uma classe mediadora para realizar as comunicações entre essas classes. A figura mostrada em 5.13 mostra com mais detalhes um diagrama de classes deste exemplo. Conforme pode ser observado na figura 5.13, está sendo utilizada uma classe para mediar a comunicação entre as classes Flight e Runway. Esta classe chama-se ATCMediator, e representa o controlador de tráfego aéreo (Air Traffic Controller) do aeroporto ilustrado por este exemplo. Este controlador é o responsável por mediar as comunicacões entre os vôos e a pista de pouso, permitindo que um determinado pouso ocorra ou seja adiado, dependendo das condições da pista do aeroporto. Neste especı́fico exemplo, é modelada apenas a situação de pouso dos aviões, para facilitar o entendimento do exemplo. Padrão Mediator 156 Figura 5.13: Diagrama de classes do exemplo para o padrão Mediator. 5.6.2 Comparação das implementações em Java e ooErlang Observando o diagrama de classes mostrado na figura 5.13, observa-se a utilização de duas interfaces. Uma delas é a interface Command, cujas classes que implementam são Flight e Runway. Essas classes implementam o método land(), responsável diretamente pelo pouso do avião no aeroporto. A outra interface chama-se IATCMediator, sendo esta a interface implementada pelo mediador ATCMediator. Este mediador é o responsável por controlar o tráfego de pousos no aeroporto. A interface IATCMediator possui os protótipos dos métodos a serem implementados pela classe ATCMediator. É por meio destes métodos que realiza-se a comunicação entre as classes Flight e Runway, de acordo com a definição do padrão Mediator. Por estarem implementando a interface em comum (Command), essas classes possuem uma implementação semelhante. O principal método implementado por elas chama-se land(). Os códigos mos- Padrão Mediator 157 trados em 5.6.1 e 5.6.2 apresentam a implementação da classe Flight em Java e ooErlang, respectivamente. 1 2 public class Flight implements Command { private IATCMediator atcMediator; 3 4 5 6 public Flight(IATCMediator atcMediator) { this.atcMediator = atcMediator; } 7 8 9 10 11 12 13 14 public void land() { if (atcMediator.isLandingOk()) { System.out.println("Landing done...."); atcMediator.setLandingStatus(true); } else System.out.println("Will wait to land...."); } Código 5.6.1: Classe Flight em Java 1 2 3 4 -class(flight). -implements(command). -export([new/1, land/0, get_ready/0]). -constructor([new/1]). 5 6 attributes. 7 8 AtcMediator. 9 10 methods. 11 12 13 new(AtcMediator) -> self::AtcMediator = AtcMediator. 14 15 16 17 18 19 20 21 22 23 24 land() -> AtcMed = self::AtcMediator, Result = AtcMed::is_landing_ok(), if (Result == true) -> io:format("Landing done....~n"), AtcMed::set_landing_status(true); true -> io:format("Will wait to land....~n") end. Código 5.6.2: Classe Flight em ooErlang Conforme pode ser observado, a classe Flight possui um atributo chamado atcMediator, que corresponde exatamente à instância da classe mediadora ATCMediator, que irá Padrão Mediator 158 se comunicar com a instância da classe Flight. Na classe Runway isto ocorre de forma semelhante. Além do construtor padrão, verifica-se a implementação do método land(). Na classe Flight, nesse método, é feita uma verificação para constatar se o pouso está permitido ou não. Na classe Runway, o método land() autoriza o pouso do avião. A autorização ou não autorização do pouso de um avião é verificada por meio de um atributo do tipo boleano, chamado “land”, que, ao possuir um valor verdadeiro, significa pouso autorizado e, em caso contrário, pouso não autorizado. Este atributo pertence à classe que implementa a interface IATCMediator, chamada ATCMediator. Esta é a principal classe deste exemplo, pois representa o próprio mediador especificado na definição deste padrão. Os códigos mostrados em 5.6.3 e 5.6.4 apresentam a implementação desta classe em Java e ooErlang. 1 2 3 4 public class ATCMediator implements IATCMediator { private Flight flight; private Runway runway; public boolean land; 5 public void registerRunway(Runway runway) { this.runway = runway; } 6 7 8 9 public void registerFlight(Flight flight) { this.flight = flight; } 10 11 12 13 public boolean isLandingOk() { return land; } 14 15 16 17 public void setLandingStatus(boolean status) { land = status; 18 19 20 } 21 22 } Código 5.6.3: Classe ATCMediator em Java Além do atributo boleano land, a classe ATCMediator possui os atributos flight e runway, que recebem as instâncias dos objetos a se comunicar por meio deste mediador. Esta classe também possui dois métodos responsáveis por registrar os objetos a serem mediados, registerRunway() e registerFlight(). Esse registro é necessário para que a comunicação se faça possivel. Os outros métodos da classe ATCMediator referem-se especificamente ao Padrão Mediator 1 2 3 4 159 -class(atcMediator). -implements(iatcMediator). -export([register_runway/1, register_flight/1]). -export([is_landing_ok/0, set_landing_status/1]). 5 6 attributes. 7 8 9 10 Flight; Runway; Land. 11 12 methods. 13 14 15 register_runway(Runway) -> self::Runway = Runway. 16 17 18 register_flight(Flight) -> self::Flight = Flight. 19 20 21 is_landing_ok() -> self::Land. 22 23 24 set_landing_status(Status) -> self::Land = Status. Código 5.6.4: Classe ATCMediator em ooErlang atributo land(), possibilitando modificar o valor desta variável assim como retornar o valor atual. Com o controle do status de pouso armazenado na classe ATCMediator, verifica-se que, para que haja a comunicação entre as classes Flight e Runway, é necessário comunicar-se por intermédio deste mediador. Assim as comunicações entre essas classes centralizam-se no mediador, sendo esta a principal vantagem da utilização do padrão Mediator, por facilitar a extensão e a manutenção desta modelagem. A classe principal, chamada MediatorExample testa as implementações realizadas e seu código em Java e ooErlang está mostrado, nesta ordem, em 5.6.5 e 5.6.6. O método principal (main) da classe MediatorExample tem por objetivo testar as implementações realizadas. Inicialmente é instanciado um objeto da classe ATCMediator, para ser o mediador. Em seguida são instanciados os objetos que irão comunicar-se por meio do mediador, Flight e Runway. Na instanciação destes objetos, o objeto mediador é passado como parâmetro. Prosseguindo, os dois objetos são registrados no objeto mediador e assim o mediador passa a poder interagir com cada objeto da mesma forma com que cada objeto Padrão Mediator 1 2 160 public class MediatorExample { public static void main(String args[]) { 3 IATCMediator atcMediator = new ATCMediator(); Flight sparrow101 = new Flight(atcMediator); Runway mainRunway = new Runway(atcMediator); 4 5 6 7 atcMediator.registerFlight(sparrow101); atcMediator.registerRunway(mainRunway); 8 9 10 sparrow101.getReady(); sparrow101.land(); mainRunway.land(); sparrow101.land(); 11 12 13 14 } 15 16 } Código 5.6.5: Classe principal MediatorExample em Java 1 2 -class(mediatorExample). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 main() -> AtcMediator = atcMediator::new_(), Sparrow101 = flight::new(AtcMediator), MainRunway = runway::new(AtcMediator), 10 11 12 AtcMediator::register_flight(Sparrow101), AtcMediator::register_runway(MainRunway), 13 14 15 16 17 Sparrow101::get_ready(), Sparrow101::land(), MainRunway::land(), Sparrow101::land(). Código 5.6.6: Classe principal MediatorExample em ooErlang pode interagir com o mediador. Após todas as instanciações necessárias para o teste da modelagem, é feito um teste para verificar se o mediador está de fato controlando os dois objetos que devem comunicarse. Primeiramente o objeto da classe Flight tenta pousar sem que a pista esteja liberada. Em seguida, o objeto da classe Runway libera a pista e, para finalizar, novamente o pouso é testado, esperando-se que, neste segundo caso, seja bem sucedido. As figuras mostradas em 5.14 e 5.15 mostram o resultado dos testes realizados em Java e ooErlang, nesta ordem. Padrão Mediator 161 Figura 5.14: Execução do exemplo para o padrão Mediator em Java. O método getReady() utilizado pelo objeto da classe Flight tanto na implementação em Java mostrada em 5.6.5, quanto em ooErlang, mostrada em 5.6.6 tem por objetivo apenas mostrar na tela uma mensagem informando que o avião está se preparando para pousar. Este método não utiliza-se do mediador para comunicar-se com o outro objeto, possuindo apenas o intuito de informação ao usuário. Ao utilizar-se de um objeto mediador, verifica-se que os objetos que precisam se comunicar passam a tornar-se mais independentes um do outro, uma vez que não comunicam-se diretamente e sim por meio de um objeto mediador. Desta forma, a modificação do fonte em algum destes objetos que necessita comunicar-se, gera muito menos impacto no sistema como um todo, facilitando a inclusão e exclusão de objetos passı́veis de comunicacão. A utilização deste padrão simplifica a referida comunicação. A extensão da linguagem Erlang mostrou-se, levando-se em consideração os resultados Padrão Mediator 162 Figura 5.15: Execução do exemplo para o padrão Mediator em ooErlang. obtidos nos testes realizados, apta a utilizar o conceito de desacoplamento de objetos na aplicação do padrão de projetos Mediator. Este desacoplamento permite que objetos que necessitem interagir possam trocar informações sem que seja necessário conhecerem um ao outro diretamente. Os resultados obtidos na implementação deste padrão em ooErlang mostraram-se semelhantes ao resultado obtido com a implementação em Java. Observa-se que o controle das comunicações torna-se centralizado com a utilização dos conceitos do padrão Mediator. Outro detalhe a ser observado é que, ao utilizar-se deste padrão, a interação dos objetos relacionados com o objeto mediador torna-se mais clara. Com uma comunicação um-para-muitos, definida pelo padrão de projetos Mediator, a facilidade de entendimento, manutenção e extensão é bem maior do que uma comunicação muitos-para-muitos, que torna-se bastante complexa dependendo da quantidade de objetos envolvidos. Padrão Memento 5.7 163 Padrão Memento De acordo com sua definição, o padrão de projetos Memento tem o objetivo de: “Sem violar o encapsulamento, capturar e externalizar o estado interno de um objeto para que o objeto possa ter esse estado restaurado posteriormente” [Gamma et al., 1994]. Quando um sistema possui a necessidade de restaurar um estado anteriormente configurado, a utilização do padrão de projetos Memento é recomendada, pois permite salvar estados internos de um objeto, restaurando-os quando se faça necessário. Sem a utilização do padrão de projetos Memento, para se externalizar o estado atual de um objeto, pode ser necessário expor a estrutura interna do referido objeto. Uma interface que mostrasse detalhes da implementação de um objeto iria quebrar o encapsulamento do objeto. Portanto, para evitar problemas de falta de encapsulamento de um objeto, utilizase o padrão Memento que permite salvar e restaurar estados internos de um objeto sem violar seu encapsulamento. 5.7.1 Exemplo utilizado para este padrão Para exemplificar a utilização do padrão Memento em uma modelagem, é utilizado um exemplo de implementação deste padrão retirado de [Making, 2013], em que é modelado um sistema simples e capaz de criar diferentes estados internos de um objeto. Além da criação destes estados, é possı́vel salvar os estados internos aos quais se deseje restaurar posteriormente. Mais de um salvamento de estados pode ser realizado e, tendo-se diversos estados salvos, é possivel retornar a cada um no momento necessário. Para implementar este exemplo, quatro (4) classes principais foram utilizadas, Memento, referente à classe que armazena os dados internos de um estado que se queira salvar, Originator, responsável por originar os estados internos do objeto a serem salvos ou não, Caretaker, sendo a classe que armazena os estados a serem salvos assim como retorna os estados previamente salvos e MementoExample, tratando-se da classe principal do exemplo. A figura 5.16 mostra um diagrama de classes deste exemplo. Padrão Memento 164 Figura 5.16: Diagrama de classes do exemplo para o padrão Memento. 5.7.2 Comparação das implementações em Java e ooErlang A classe Memento, conforme explicada anteriormente, é a responsável por ser a estrutura que armazena um estado interno do objeto. Sempre que se queira salvar um novo estado interno, é criada uma nova instância desta classe. Para cada estado que tiver sido salvo, existe uma correspondente instância da classe Memento responsável por armazenar o referido estado. Os códigos mostrados em 5.7.1 e 5.7.2 apresentam a implementação da classe Memento em Java e ooErlang, nesta ordem. 1 2 public class Memento { private String state; 3 public Memento(String stateToSave) { state = stateToSave; } 4 5 6 7 public String getSavedState() { return state; } 8 9 10 11 } Código 5.7.1: Classe Memento em Java Observa-se que a classe Memento possui apenas um atributo chamado state. Este atributo tem por objetivo armazenar o estado de um objeto a ser salvo e que, posteriormente poderá ser restaurado. O construtor desta classe recebe como parâmetro de entrada o estado a ser armazenado no atributo state. Além disso, a classe Memento possui um método chamado getSavedState, que tem por principal função a de retornar o estado que foi arma- Padrão Memento 1 2 3 165 -class(memento). -export([new/1, get_saved_state/0]). -constructor([new/1]). 4 5 attributes. 6 7 State. 8 9 methods. 10 11 12 new(StateToSave) -> self::State = StateToSave. 13 14 15 get_saved_state() -> self::State. Código 5.7.2: Classe Memento em ooErlang zenado na instanciação do objeto desta classe, permitindo que um estado anteriormente salvo possa ser restaurado. As classes Originator e Caretaker são responsáveis por criar estados e salvar estados, respectivamente. A classe Originator armazena o estado atual de sua instância internamente. Quando é necessário que o estado atual seja salvo para posterior reutilização, a classe Caretaker o armazena em uma lista. Desta forma é possı́vel salvar vários estados, pois todos eles são armazenados em uma lista de estados salvos. Os códigos 5.7.3 e 5.7.4 mostram a implementação da classe Originator em Java e ooErlang. Assim como a classe Memento, a classe Originator também possui um atributo chamado state. Mas em relação a esta classe, este atributo armazena apenas o estado atual do objeto. Este estado pode ser modificado a qualquer momento com a utilização do método set() que recebe como parâmetro de entrada um novo estado, e o armazena no atributo state. Além desse método, outros dois métodos também podem ser observados: saveToMemento(), responsável por salvar o estado atual e restoreFromMemento() que restaura um estado previamente salvo. As ações de salvar e restaurar um estado estão implementadas na classe Originator porém é na classe Caretaker que a lista de estados salvos de fato fica armazenada. Ao observar o método saveToMemento(), verifica-se que é instanciado um objeto da classe Memento passando como parâmetro do construtor o estado atual. Em seguida este objeto é retornado. Isto é realizado desta forma pois este método é utilizado como parâmetro de Padrão Memento 1 2 166 public class Originator { private String state; 3 public void set(String state) { System.out.println("Originator: Setting state to " + state); this.state = state; } 4 5 6 7 8 public Memento saveToMemento() { System.out.println("Originator: Saving to Memento."); return new Memento(state); } 9 10 11 12 13 public void restoreFromMemento(Memento m) { state = m.getSavedState(); System.out.println("Originator: State after restoring from Memento: " + state); } 14 15 16 17 18 19 } Código 5.7.3: Classe Originator em Java 1 2 -class(originator). -export([set/1, save_to_memento/0, restore_from_memento/1]). 3 4 attributes. 5 6 State. 7 8 methods. 9 10 11 12 set(State) -> io:format("Originator: Setting state to ~p~n", [State]), self::State = State. 13 14 15 16 save_to_memento() -> io:format("Originator: Saving to Memento.~n"), memento::new(self::State). 17 18 19 20 21 restore_from_memento(M) -> self::State = M::get_saved_state(), io:format("Originator: State after restoring from memento: ~p~n", [self::State]). Código 5.7.4: Classe Originator em ooErlang entrada do método addMemento() da classe Caretaker, que armazena o objeto Memento na lista de estados salvos. Assim, quando um estado atual qualquer precisa ser salvo, é utilizado o método addMemento() da classe Caretaker. Este método simplesmente recebe como parâmetro de Padrão Memento 167 entrada um objeto da classe Memento e o inclui na lista de estados salvos. Mas esse objeto é obtido com a utilização do método saveToMemento() que, conforme visto anteriormente, retorna um objeto do tipo Memento armazenando o estado atual. A classe principal MementoExample mostrada nos códigos 5.7.5 e 5.7.6 em Java e ooErlang mostram melhor o processo de salvamento e restauração de estados. 1 2 3 4 public class MementoExample { public static void main(String[] args) { Caretaker caretaker = new Caretaker(); Originator originator = new Originator(); 5 originator.set("State1"); originator.set("State2"); caretaker.addMemento(originator.saveToMemento()); originator.set("State3"); 6 7 8 9 10 originator.restoreFromMemento(caretaker.getMemento(0)); originator.set("State4"); 11 12 13 caretaker.addMemento(originator.saveToMemento()); originator.set("State5"); 14 15 16 originator.restoreFromMemento(caretaker.getMemento(1)); 17 } 18 19 } Código 5.7.5: Classe principal MementoExample em Java Conforme pode ser observado nas implementações da classe MementoExample, inicialmente são instanciados dois objetos, um do tipo Caretaker e outro do tipo Originator. Em seguida, o objeto da classe Originator passa a criar estados interiores e, após ter criado o segundo estado, o objeto da classe Caretaker é utilizado para salvar este estado na primeira posição da lista de estados salvos. Os estados são salvos sequencialmente na lista de estados salvos da classe Caretaker, em ordem crescente de posição. Após o segundo estado ter sido salvo, o objeto da classe Originator cria um terceiro estado e, em seguida, o método restoreFromMemento() também da classe Originator é utilizado para retornar o primeiro estado salvo na lista de estados salvos da classe Caretaker. Em seguida outros estados são criados e mais um estado é salvo e restaurado. O resultado da execução deste exemplo pode ser visto nas figuras 5.17 e 5.18 em que aparecem as execuções em Java e ooErlang respectivamente. Verifica-se nas implementações da classe principal MementoExample que, levando-se Padrão Memento 1 2 168 -class(mementoExample). -export([main/0]). 3 4 class_methods. 5 6 7 8 main() -> Caretaker = caretaker::new(), Originator = originator::new_(), 9 10 11 12 13 Originator::set("State1"), Originator::set("State2"), Caretaker::add_memento(Originator::save_to_memento()), Originator::set("State3"), 14 15 16 Originator::restore_from_memento(Caretaker::get_memento(0)), Originator::set("State4"), 17 18 19 Caretaker::add_memento(Originator::save_to_memento()), Originator::set("State5"), 20 21 Originator::restore_from_memento(Caretaker::get_memento(1)). Código 5.7.6: Classe principal MementoExample em ooErlang em consideração o fato de cada estado salvo estar sendo representado por um objeto da classe Memento armazenado em uma lista na classe Caretaker, sempre que se necessite retornar a um estado previamente salvo, deve-se informar qual estado é esse. No exemplo mostrado, dois estados são salvos e duas restaurações de estados são realizadas. Na primeira restauração é chamado o estado salvo na primeira posição da lista e na segunda restauração é utilizada a segunda posição da lista. Muitas vezes, na modelagem de um sistema é necessário que se salve um estado atual de determinado objeto, tanto para que o usuário possa recuperar uma informação necessária quanto para desfazer uma ação não necessária ou errada. O padrão de projetos Memento, ao ser implementado na extensão da linguagem de programação Erlang, apresentou comportamento semelhante à implementação em Java, comprovando que esta extensão tem a capacidade de suportar sistemas em que seja possı́vel salvar estados de objetos. Conforme determina a definição do padrão, com a utilização de uma classe para armazenar a lista de estados salvos e com a modelagem de classes que permite inserir elementos nesta lista assim como retirar elementos, torna-se possivel salvar e restaurar estados internos de um objeto sem que seja necessário expor detalhes internos de implementação destes objetos. Desta forma o encapsulamento do objeto continua intacto e o usuário final não Padrão Memento 169 Figura 5.17: Execução do exemplo para o padrão Memento em Java. precisa saber a estrutura utilizada para armazenar os estados salvos. Ao utilizar-se do padrão de projetos Memento verifica-se que, além de o encapsulamento não ser violado, a classe que origina os estados internos do objeto (neste exemplo a classe Originator) não necessita armazenar os estados salvos do objeto. Desta forma a classe Originator torna-se mais simples pois os estados salvos são armazenados na classe Caretaker. Uma consequência ruim da utilização deste padrão é a baixa eficiência no caso de ser necessário salvar uma grande quantidade de informações ou estados internos de um objeto. Padrão Observer 170 Figura 5.18: Execução do exemplo para o padrão Memento em ooErlang. 5.8 Padrão Observer Este padrão de projetos, de acordo com sua definição, possui o principal objetivo de: “Definir uma dependência um-para-muitos entre objetos para que quando um objeto mudar de estado, todos os seus dependentes sejam notificados e atualizados automaticamente” [Gamma et al., 1994]. Percebe-se que, um sistema que possua a caracterı́stica de ter uma classe ligada a várias classes dependentes que necessitem estar atualizadas em relação às mudanças desta classe, pode utilizar o padrão de projetos Observer em sua modelagem. As classes dependentes da classe a ser observada são conhecidas na utilização deste padrão de projetos como “observadores”. A classe observada é a classe “sujeito”, que estará sendo observada pelas classes observadoras. Portanto, este padrão de projetos pode ser usado quando uma abstração possui dois aspectos, um dependente do outro. O aspecto dependente é o observador. O aspecto dependente geralmente pode ser composto por mais de uma classe e, ao utilizar este padrão, a classe observada não precisa conhecer os observadores, tornando o sistema mais fácil de ser reutilizável. Padrão Observer 5.8.1 171 Exemplo utilizado para este padrão O exemplo utilizado para ilustrar a utilização do padrão de projetos Observer foi retirado de [Freeman et al., 2004] e trata-se de um sistema gerenciador de clima atmosférico. Esse sistema recebe como parâmetros de entrada três fatores climáticos (temperatura, umidade e pressão) e gerencia esses dados retornando diversos tipos de informações a respeito do clima atual. É um sistema que trabalha de forma a prover a previsão do tempo de acordo com os dados recebidos da atmosfera. A classe observada chama-se WeatherData e armazena os dados atmosféricos atualizados. Sempre que ocorre uma mudança nesses dados, esta classe informa aos observadores as mudanças para atualização. Os observadores são classes que mostram diversas caracterı́sticas do clima. As classes referentes aos observadores são: StatisticsDisplay (estatı́sticas da temperatura), ForecastDisplay (pressão atmosférica), HeatIndexDisplay (ı́ndice de calor) e CurrentConditionsDisplay (temperatura e umidade atual). A figura 5.19 mostra um diagrama de classes deste exemplo. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. 5.8.2 Comparação das implementações em Java e ooErlang Conforme pode ser observado no diagrama mostrado em 5.19, nesta modelagem foram implementadas três interfaces: Subject, interface implementada pela classe observada chamada WeatherData, Observer, sendo esta a interface implementada por todas as classes observadoras e DisplayElement, que corresponde à interface implementada pelas classes que mostram na tela os dados atmosféricos já interpretados. Todas as classes que implementam a interface Observer também implementam a interface DisplayElement. As classes observadoras da classe WeatherData também são responsáveis por mostrar um aspecto diferente do clima atmosférico. Uma delas é a classe CurrentConditionsDisplay. Esta classe tem por objetivo mostrar, em tempo real, os dados atmosféricos referentes à temperatura e umidade. Desta forma, esta classe está sempre sendo notificada pela classe observada WeatherData (que possui os dados atualizados de temperatura, umidade e pressão). Os códigos mostrados em 5.8.1 e 5.8.2 apresentam a implementação da classe CurrentConditionsDisplay em Java e ooErlang, nesta ordem. Padrão Observer 172 Figura 5.19: Diagrama de classes do exemplo para o padrão Observer. Conforme pode ser observado nas implementações da classe CurrentConditionsDisplay, dois atributos tem relação com os dados atmosféricos: temperature (temperatura) e humidity (umidade). Nesta classe não existe o atributo para a pressão pois a instância da classe CurrentConditionsDisplay irá apenas mostrar a temperatura e umidade atual. Além disso existe o atributo weatherData e este atributo recebe a instância da classe observada, WeatherData sempre que o construtor for utilizado para instanciar um objeto desta classe. A classe CurrentConditionsDisplay também possui o método update(). Este é o método principal da classe e é responsável por receber todas as modificações nos dados atmosféricos Padrão Observer 1 2 3 4 173 public class CurrentConditionsDisplay implements Observer, DisplayElement { private float temperature; private float humidity; private Subject weatherData; 5 public CurrentConditionsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } 6 7 8 9 10 public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; display(); } 11 12 13 14 15 16 public void display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity"); } 17 18 19 20 21 } Código 5.8.1: Classe CurrentConditionsDisplay em Java que ocorrerem na classe observada WeatherData. Dentro deste método existe a invocação de outro método, chamado display(), sendo este método responsável por mostrar os dados atmosféricos com o qual esta classe trabalha. Todas as classes observadoras possuem os métodos update() e display(). A classe observada WeatherData é responsável por guardar os dados atmosféricos de temperatura, umidade e pressão que o sistema receber como parâmetros de entrada. Para cada atualização nestes dados, a classe observada notifica todas as classes observadoras para que as mesmas possam atualizar seus dados e mostrar ao usuário final os dados em tempo real. A notificação dos observadores ocorre por meio do método update() e a visualização por meio do método display(). Os códigos em 5.8.3 e 5.8.4 mostram a implementação da classe WeatherData em Java e ooErlang. Nas implementações apresentadas em 5.8.3 e 5.8.4 apenas os detalhes principais estão mostrados. Ao observar os atributos da classe WeatherData, percebe-se a utilização de um arranjo chamado observers. Neste arranjo os observadores são armazenados para que possam ser futuramente notificados. Além disso, os atributos temperature, humidity e pressure armazenam os dados atuais de temperatura, umidade e pressão recebidos pelo sistema. Ao instanciar um objeto da classe WeatherData, a lista de observadores é criada Padrão Observer 1 2 3 4 174 -class(currentConditionsDisplay). -implements([observer,displayElement]). -export([new/1, update/3, display/0]). -constructor([new/1]). 5 6 attributes. 7 8 9 10 Temperature; Humidity; WeatherData. 11 12 methods. 13 14 15 16 17 new(WeatherData) -> self::WeatherData = WeatherData, Temp = self::WeatherData, Temp::register_observer({currentConditionsDisplay, ObjectID}). 18 19 20 21 22 update(Temperature, Humidity, Pressure) -> self::Temperature = Temperature, self::Humidity = Humidity, display(). 23 24 25 26 display() -> io:format("Current conditions: ~p F degrees and ~p % humidity ~n", [self::Temperature, self::Humidity]). Código 5.8.2: Classe CurrentConditionsDisplay em ooErlang para armazenar os objetos que implementam a interface Observer. O método setMeasurements da classe WeatherData recebe os dados atmosféricos mais recentes para serem atualizados nos atributos desta classe. Logo que os atributos são atualizados, é invocado o método measurementsChanged() que tem por objetivo iniciar o processo de notificação dos observadores para que atualizem os novos dados atmosféricos. Este método invoca o método notifyObservers() que, por sua vez, percorre toda a lista do observadores, notificando um a um das mudanças ocorridas nos dados atmosféricos, para que cada observador atualize seus próprios dados. Todos os observadores (CurrentConditionsDisplay, ForecastDisplay, HeatIndexDisplay, e StatisticsDisplay) não utilizam todos os dados atmosféricos recebidos, apenas aqueles que lhes interessa. A classe ForecastDisplay por exemplo, utiliza apenas a pressão atmosférica para verificar se o clima está propenso a chuvas ou a um clima ensolarado. Os dados atmosféricos recentes recebidos pelo sistema são lidos na classe principal, WeatherStation, mostrada nos códigos 5.8.5 e 5.8.6 (Java e ooErlang respectivamente). Padrão Observer 1 2 3 4 5 175 public class WeatherData implements Subject { private ArrayList observers; private float temperature; private float humidity; private float pressure; 6 7 8 9 public WeatherData() { observers = new ArrayList(); } 10 11 12 13 public void registerObserver(Observer o) { observers.add(o); } 14 15 16 17 18 19 20 public void notifyObservers() { for (int i = 0; i < observers.size(); i++) { Observer observer = (Observer)observers.get(i); observer.update(temperature, humidity, pressure); } } 21 22 23 24 public void measurementsChanged() { notifyObservers(); } 25 26 27 28 29 30 31 32 public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } Código 5.8.3: Classe WeatherData em Java A classe principal WeatherStation inicialmente instancia um objeto da classe WeatherData pra que o mesmo possa funcionar como a classe “sujeita”, ou “observada” pelas classes observadoras. Em seguida as quatro (4) classes observadoras são instanciadas: CurrentConditionsDisplay, StaticticsDisplay, ForecastDisplay e HeatIndexDisplay. Para todas as classes observadoras, a instância da classe WeatherData é passada como parâmetro de entrada para que, no momento em que são instanciadas, já recebam em seu atributo weatherData a respectiva classe observada. No construtor de cada classe observadora, além de ser recebido como parâmetro de entrada a instância da classe a ser observada, é utilizado o método registerObserver(). Este método, da classe WeatherData, tem o objetivo de incluir observadores na lista chamada observers. Ao ser chamado no construtor de uma classe observadora, esta classe auto- Padrão Observer 1 2 3 176 -class(weatherData). -implements(subject). -constructor([new/0]). 4 5 attributes. 6 7 8 9 10 Observers; Temperature; Humidity; Pressure. 11 12 methods. 13 14 15 new() -> self::Observers = []. 16 17 18 register_observer(Observer) -> self::Observers = [Observer | self::Observers]. 19 20 21 22 notify_observers() -> Observers = self::Observers, notify_aux(Observers). 23 24 25 measurements_changed() -> notify_observers(). 26 27 28 29 30 31 set_measurements(Temperature, Humidity, Pressure) -> self::Temperature = Temperature, self::Humidity = Humidity, self::Pressure = Pressure, measurements_changed(). Código 5.8.4: Classe WeatherData em ooErlang maticamente já torna-se um dos observadores registrados no atributo observers da classe WeatherData, estando, dessa forma, apta a ser notificada sobre qualquer mudança ocorrida nos dados atmosféricos. No método principal da classe WeatherStation (main), após serem realizadas as respectivas instanciações de objetos para cada uma das classes observadoras, passa-se a, de fato, testar o modelo que utilizou-se do padrão de projetos Observer. Este teste é realizado três vezes, com a utilização do método setMeasurements, da classe WeatherData. Portanto, o objeto da classe observada chama esse método três vezes, passando diferentes dados atmosféricos, para serem atualizados nos observadores. As figuras mostradas em 5.20 e 5.21 mostram a execução deste exemplo em Java e ooErlang, nesta ordem. O padrão de projetos Observer é um dos mais utilizados padrões, sendo incrivelmente Padrão Observer 1 177 public class WeatherStation { 2 public static void main(String[] args) { WeatherData weatherData = new WeatherData(); 3 4 5 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData); HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData); 6 7 8 9 10 11 12 13 14 weatherData.setMeasurements(80, 65, 30.4f); weatherData.setMeasurements(82, 70, 29.2f); weatherData.setMeasurements(78, 90, 29.2f); 15 16 17 } 18 19 } Código 5.8.5: Classe principal WeatherStation em Java 1 2 -class(weatherStation). -export([main/0]). 3 4 class_methods. 5 6 7 main() -> WeatherData = weatherData::new(), 8 9 10 11 12 HeatIndexDisplay = heatIndexDisplay::new(WeatherData), ForecastDisplay = forecastDisplay::new(WeatherData), StatisticsDisplay = statisticsDisplay::new(WeatherData), CurrentConditions = currentConditionsDisplay::new(WeatherData), 13 14 15 16 WeatherData::set_measurements(80, 65, 30.4), WeatherData::set_measurements(82, 70, 29.2), WeatherData::set_measurements(78, 90, 29.2). Código 5.8.6: Classe principal WeatherStation em ooErlang útil [Freeman et al., 2004]. Este padrão pode ser bem aplicado sempre que se tenha uma situação em que diversos objetos precisem estar sendo atualizados de acordo com os dados internos de um objeto em comum. O objeto em comum chama-se objeto observado e os objetos que são constantemente atualizados são chamados de objetos observadores, por estarem constantemente recebendo as atualizações do objeto observado. No exemplo utilizado, é modelado um sistema para uma estação que gerencia dados Padrão Observer 178 Figura 5.20: Execução do exemplo para o padrão Observer em Java. atmosféricos. A classe observada armazena todos os dados atmosféricos coletados, sendo que, a cada atualização de valores recebidos, todas as classes observadoras passam a ser notificadas. Cada classe observadora mostra um dado especı́fico da atmosfera, relacionado à temperatura, umidade ou pressão. Portanto, torna-se necessário que cada uma dessas classes observadoras esteja sendo constantemente atualizada das mudanças ocorridas nos referidos dados. A extensão da linguagem Erlang, ooErlang, mostrou, por meio de uma implementação similar à utilizada no exemplo em Java, resultados semelhantes aos mostrados na execução do fonte em Java. Dessa forma, é possivel verificar que esta extensão suporta a utilização de estruturas que implementem o padrão de projetos Observer, tornando este padrão aplicável em sistemas que estejam sendo modelados utilizando-se do ooErlang. Na utilização do padrão Observer verifica-se que a classe observada e as classes observa- Padrão State 179 Figura 5.21: Execução do exemplo para o padrão Observer em ooErlang. doras ficam desacopladas. Deste modo é possı́vel realizar mudanças na classe observadora sem que isto cause um impacto grande no sistema como um todo. Consequentemente, o sistema torna-se mais fácil de ser estendido ou mantido, pois o processo de inserção e retirada de observadores é realizado também sem que o sistema precise ser refatorado. A classe observada não precisa ter conhecimento das classes observadoras, sendo necessário apenas ter acesso à lista de observadores. 5.9 Padrão State O padrão de projetos intitulado State tem, de acordo com sua definição, o objetivo de: “Permitir a um objeto alterar o seu comportamento quando o seu estado interno mudar. O objeto irá aparentar mudar de classe” [Gamma et al., 1994]. Em sistemas onde haja a necessidade de se utilizar um ou vários objetos que precisem ter diferentes comportamentos em virtude de seus estados interiores, a utilização do padrão State é recomendável. Ao Padrão State 180 utilizar-se deste padrão de projetos, um objeto passa a comportar-se de acordo com certo estado interior. Desta forma, é importante perceber que o padrão de projetos State pode ser aplicado em situações onde um comportamento de um objeto pode e deve mudar em tempo de execução, de acordo com o estado atual em que este objeto se encontra. Outra situação em que este padrão pode ser utilizado é quando um sistema possui operações com diversas estruturas condicionais, e cada uma dessas estruturas depende de um estado especı́fico de um determinado objeto. Ou seja, sempre que houver a necessidade de tratamento do estado interno de um objeto, o padrão State é utilizável. 5.9.1 Exemplo utilizado para este padrão No exemplo, retirado de [Freeman et al., 2004], utilizado para ilustrar a utilização do padrão State, é modelado um sistema gerenciador de uma máquina de gomas de mascar. Este sistema deve controlar as ações realizadas pelos usuários nesta máquina, verificando a quantidade de gomas de mascar a máquina ainda possui, realizando as ações devidas quando um usuário insere uma moeda entre outras coisas. Esta máquina possui diferentes estados e cada ação realizada deverá levar em consideração os estados internos desta máquina. Definiu-se uma classe para cada estado criado na máquina de gomas de mascar, referente às diferentes ações realizadas nesta máquina. No total estão definidos quatro (4) estados, e as classes são as seguintes: NoQuarterState, para quando a máquina estiver com gomas e sem moeda inserida, HasQuarterState para quando houver uma moeda inserida na máquina, SoldState, representando a venda de uma goma de mascar e SoldOutState para ser utilizado quando todas as gomas forem vendidas. A figura mostrada em 5.22 mostra um diagrama de classes deste exemplo. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Além das classes representando cada estado no qual a máquina de gomas de mascar pode estar sendo representada dependendo de seu estado e das ações realizadas pelos usuários, modelou-se uma interface chamada State, que é implementada por todas as classes de estados da máquina. Esta interface possui os quatro principais métodos que são as ações possı́veis de serem realizadas na máquina. A classe GumballMachine representa a máquina Padrão State 181 Figura 5.22: Diagrama de classes do exemplo para o padrão State. de gomas em si, possuindo instâncias das classes de estados e a classe GumballMachineTestDrive é a classe principal, da qual os testes podem ser realizados. 5.9.2 Comparação das implementações em Java e ooErlang As quatro classes representando os diferentes estados da máquina implementam os métodos da interface State. Os métodos são: InsertQuarter, utilizado para inserção de uma moeda e compra da goma de mascar, EjectQuarter, quando a máquina não tem mais gomas e precisa devolver uma moeda inserida, TurnCrank, ação de girar manivela para receber a goma e Dispense, para recebimento de goma. Os códigos da classe NoQuarterState são mostrados em 5.9.1 e 5.9.2 em Java e ooErlang, respectivamente. Como pode ser visto nas implementações da classe NoQuarterState, existe um atributo Padrão State 1 2 182 public class NoQuarterState implements State { GumballMachine gumballMachine; 3 4 5 6 public NoQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } 7 8 9 10 11 public void insertQuarter() { System.out.println("You inserted a quarter"); gumballMachine.setState(gumballMachine.getHasQuarterState()); } 12 13 14 15 public void ejectQuarter() { System.out.println("You haven’t inserted a quarter"); } 16 17 18 19 public void turnCrank() { System.out.println("You turned, but there’s no quarter"); } 20 21 22 23 public void dispense() { System.out.println("You need to pay first"); } Código 5.9.1: Classe NoQuarterState em Java chamado gumballMachine que recebe a instância da classe GumballMachine no construtor desta classe. Cada uma das ações realizadas na máquina estão implementadas na classe NoQuarterState, para que todas as ações sejam tratadas e não haja situação com falta de tratamento. Por exemplo, se for utilizado o método EjectQuarter para devolver uma moeda e a máquina estiver no estado NoQuarterState, será retornada uma mensagem informando que nenhuma moeda foi inserida. Cada um dos quatro estados definidos neste exemplo possui uma ação adequada ao seu estado, dependendo do método utilizado pelo usuário. O método TurnCrank, por exemplo, ao ser utilizado quando há goma mas não há moeda na máquina (NoQuarterState), retorna uma mensagem informando que a manivela foi girada mas não existe moeda. Já o método InsertQuarter imprime uma mensagem informando que uma moeda foi inserida e modifica o estado da máquina para HasQuarterState, estado que representa a máquina com uma moeda inserida. A classe GumballMachine tem por objetivo representar a máquina de gomas de mascar propriamente dita. Portanto, conforme mostrado nos códigos em 5.9.3 e 5.9.4, que são as implementações desta classe em Java e ooErlang, respectivamente, esta classe possui Padrão State 1 2 3 183 -class(noQuarterState). -implements(state). -constructor([new/1]). 4 5 attributes. 6 7 GumballMachine. 8 9 methods. 10 11 12 new(GumballMachine) -> self::GumballMachine = GumballMachine. 13 14 15 16 17 insert_quarter() -> io:format("You inserted a quarter~n"), GBMachine = self::GumballMachine, GBMachine::set_state(GBMachine::get_has_quarter_state()). 18 19 20 eject_quarter() -> io:format("You haven’t inserted a quarter~n"). 21 22 23 turn_crank() -> io:format("You turned but there’s no quarter~n"). 24 25 26 dispense() -> io:format("You need to pay first~n"). Código 5.9.2: Classe NoQuarterState em ooErlang todos os métodos que controlam a máquina implementados nas classes referentes às ações e também possui outros métodos para gerenciamento da máquina. Dessa forma, todas as ações que forem realizadas pela instância da classe GumballMachine estarão definidas nas classes que implementam a interface State. Nas implementações mostradas da classe GumballMachine, apenas os detalhes principais estão sendo mostrados. Ao verificar os atributos desta classe, observam-se inicialmente quatro atributos referentes aos estados previamente definidos para a máquina de gomas de mascar. Cada um destes atributos tem por objetivo receber uma instância das classes que implementam a interface State. Outro atributo verificado chama-se state, que armazena o estado atual em que se encontra a máquina. O atributo count armazena a quantidade de gomas ainda presente na máquina. O construtor da classe GumballMachine tem por objetivo instanciar cada um dos estados, armazenando-os nos atributos de estados desta classe. Este construtor recebe como parâmetro a quantidade inicial de gomas de mascar. Se a quantidade inicial não for nula, Padrão State 1 184 public class GumballMachine { 2 3 4 5 6 State State State State soldOutState; noQuarterState; hasQuarterState; soldState; 7 8 9 State state = soldOutState; int count = 0; 10 11 12 13 public void insertQuarter() { state.insertQuarter(); } 14 15 16 17 public void ejectQuarter() { state.ejectQuarter(); } 18 19 20 21 void setState(State state) { this.state = state; } Código 5.9.3: Classe GumballMachine em Java 1 2 3 -class(gumballMachine). -export([new/1, insert_quarter/0, eject_quarter/0, turn_crank/0]). -constructor([new/1]). 4 5 attributes. 6 7 8 9 10 11 12 SoldOutState; NoQuarterState; HasQuarterState; SoldState; State; Count. 13 14 methods. 15 16 17 18 insert_quarter() -> Sta = self::State, Sta::insert_quarter(). 19 20 21 22 eject_quarter() -> Sta = self::State, Sta::eject_quarter(). 23 24 25 set_state(State) -> self::State = State. Código 5.9.4: Classe GumballMachine em ooErlang Padrão State 185 o atributo state recebe como estado inicial a instância da classe NoQuarterState. Caso contrário é iniciado como SoldOutState. Nesta mesma classe todos os atributos referentes aos tipos de ações que podem ocasionar uma mudança de estados está implementada. Conforme pode ser visto na classe GumballMachine, para qualquer um dos métodos que pode modificar o estado da máquina, o atributo que armazena o estado atual (state) é utilizado para realizar a devida ação. Assim, não é necessário se preocupar com o estado atual em que se encontra a máquina. Todas as mudanças de estado ocorrem por meio dos métodos que retornam os estados (getNoQuarterState(), getHasQuarterState() etc) e que, apesar de estarem implementados na classe GumballMachine, são chamados pelas próprias classes de estados. Para executar os testes que comprovam a correta utilização do padrão de projetos State, é utilizado o método principal da classe GumbalMachineTestDrive. Esta classe está mostrada nos códigos apresentados em 5.9.5 e 5.9.6, que representam as implementação em Java e ooErlang, nesta ordem. O método principal desta classe utiliza-se apenas de uma instância da classe GumballMachine para chamar todos os métodos que provocam mudança de estados. 1 public class GumballMachineTestDrive { 2 public static void main(String[] args) { GumballMachine gumballMachine = new GumballMachine(5); 3 4 5 System.out.println(gumballMachine); 6 7 gumballMachine.insertQuarter(); gumballMachine.turnCrank(); 8 9 10 System.out.println(gumballMachine); 11 12 gumballMachine.insertQuarter(); gumballMachine.turnCrank(); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); 13 14 15 16 17 System.out.println(gumballMachine); 18 } 19 20 } Código 5.9.5: Classe principal GumballMachineTestDrive em Java Inicialmente, na classe GumballMachineTestDrive, é instanciado um objeto da classe Padrão State 1 2 186 -class(gumballMachineTestDrive). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 main() -> GumballMachine = gumballMachine::new(5), %io:format("~p~n", [GumballMachine::to_string()]), GumballMachine::to_string(), 10 GumballMachine::insert_quarter(), GumballMachine::turn_crank(), 11 12 13 %io:format("~p~n", [GumballMachine::to_string()]), GumballMachine::to_string(), 14 15 16 GumballMachine::insert_quarter(), GumballMachine::turn_crank(), GumballMachine::insert_quarter(), GumballMachine::turn_crank(), 17 18 19 20 21 %io:format("~p~n", [GumballMachine::to_string()]), GumballMachine::to_string(). 22 23 Código 5.9.6: Classe principal GumballMachineTestDrive em ooErlang GumballMachine, passando como parâmetro o valor cinco (5), que refere-se à quantidade inicial de gomas de mascar da máquina. Em seguida, após ser impresso o estado interior da máquina, é chamado o método insertQuarter() e, como a máquina já possui gomas, o estado inicial é o da instância da classe NoQuarterState. Dentro desta classe, o estado da máquina é modificado para HasQuarterState. Ao ser chamado o método turnCrank(), os estados são modificados novamente. Após o método turnCrank() ter sido utilizado, o estado é modificado para SoldState, mostrando que a venda foi concluı́da. Após este procedimento, é realizada uma verificação no atributo count, para saber se a máquina ainda possui gomas de mascar. Em caso positivo, o estado da classe NoQuarterState é atribuı́do. Em caso contrário, o atributo state da classe GumballMachine recebe o estado da classe SoldOutState, representando que tudo foi vendido. As figuras 5.23 e 5.24 mostram as execuções em Java e ooErlang da classe principal. Ao utilizar-se do padrão de projetos State, verificou-se que cada um dos estados da máquina de gomas de mascar, por ter sido modelado para ser uma classe, ficou desacoplado Padrão State 187 Figura 5.23: Execução do exemplo para o padrão State em Java. da classe GumballMachine, que é a classe da máquina de gomas. Desta forma torna-se mais prático inserir e remover novos estados, sendo necessário apenas criar uma classe para cada novo estado, remodelando os estados atuais para que se adaptem ao novo estado, não sendo necessário haver retrabalho em todas as classes de estados. A classe principal, ao instanciar um objeto para ser a máquina de gomas de mascar, não precisa se preocupar em qual estado este objeto se encontra. Basta apenas chamar os métodos, pois cada estado está preparado para receber qualquer um dos métodos. O usuário desconhece o estado em que se encontra a máquina e, para cada nova ação existem respostas diferentes, de acordo com o estado atual. Tem-se a impressão de que o objeto mudou de classe, de acordo com as definições do padrão State. A extensão do Erlang para orientação a objetos, ooErlang mostrou um comportamento igual ao comportamento mostrado na implementação em Java. Verifica-se, desta forma, Padrão Strategy 188 Figura 5.24: Execução do exemplo para o padrão State em ooErlang. que o padrão de projetos State pode ser aplicado em situações semelhantes às trabalhadas neste exemplo com o suporte da extensão ooErlang. Assim é possı́vel trabalhar com os estados de um objeto sem se preocupar com a classe que irá possuir os mesmos, por não haver forte acoplamento entre ambos. 5.10 Padrão Strategy O padrão de projetos Strategy, por sua definição, tem como objetivo: “Definir uma famı́lia de algoritmos, encapsular cada um e fazê-los intercambiáveis. Strategy permite que algoritmos mudem independentemente entre clientes que os utilizam” [Gamma et al., 1994]. Este é um padrão de projetos que pode ser bem aplicado em sistemas com a necessidade de reutilização. Ao trabalhar com diferentes famı́lias de algoritmos encapsuladas, reutilizar estas famı́lias em outras situações, modificando-as produz menos impacto no sistema como um todo. A utilização deste padrão de projetos é recomendável em situações em que um sistema possua diversas classes relacionadas e que apenas seus comportamentos sejam diferentes. Padrão Strategy 189 Outra situação em que este padrão pode ser aplicado é quando um sistema precisa de diferentes variações de um algoritmo e, dessa forma, pode ser utilizada uma hierarquia de classes de algoritmos. Quando seja necessário manter certo algoritmo encapsulado do usuário, o padrão Strategy também pode ser utilizado evitando expor detalhes de implementação desnecessários. 5.10.1 Exemplo utilizado para este padrão No exemplo utilizado para ilustrar a aplicação deste padrão, retirado de [Freeman et al., 2004], é implementado um sistema simulador de patos. Nesse sistema são modelados diferentes tipos de patos e para cada tipo especı́fico de pato devem haver comportamentos caracterı́sticos dos mesmos. Esses comportamentos foram separados em dois grupos, o grupo dos comportamentos de vôo dos patos e o comportamento do grasnar dos patos. Neste exemplo foram modelados quatro tipos principais de patos, e cada pato é representado por uma classe, sendo eles: MallardDuck (pato real), RedheadDuck (pato com cabeça vermelha), RubberDuck (pato de borracha) e DecoyDuck (pato de decoração). Todas essas classes estendem a classeDuck, responsável por manter as informações principais de cada pato. Os comportamentos dos patos são inseridos na classe Duck, em seus atributos. A figura 5.25 mostra um diagrama de classes referente a este exemplo. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Cada um dos comportamentos está implementado em uma interface, sendo que o comportamento de vôo está definido nas classes que implementam a interface FlyBehaviour e o comportamento de grasnar está definido nas classes que implementam a interface QuackBehaviour. Neste exemplo existem dois tipos de comportamentos de vôo e três tipos de comportamentos de grasnar, conforme a quantidade de classes que implementa cada uma das interfaces referentes aos comportamentos dos patos. A classe principal desta modelagem chama-se MiniDuckSimulator. 5.10.2 Comparação das implementações em Java e ooErlang A classe Duck, conforme mostrada nos códigos 5.10.1 e 5.10.2, referente às implementações em Java e ooErlang, possui dois atributos, flyBehaviour e quackBehaviour. Estes Padrão Strategy 190 Figura 5.25: Diagrama de classes do exemplo para o padrão Strategy. atributos armazenam as instâncias das interfaces FlyBehaviour e QuackBehaviour, respectivamente. Cada uma das classes que estender a classe Duck irá, por consequência possuir estes mesmos atributos. Dessa forma, cada classe passa a possuir seus próprios comportamentos de voar e grasnar. Os métodos da classe Duck performFly() e performQuack() utilizam as interfaces que implementam os comportamentos para cada pato especı́fico. Cada classe pato os herda, assim como herda também o método swim(), que para qualquer pato, é o mesmo. A classe MallardDuck, mostrada nos códigos 5.10.3 e 5.10.4 é semelhante às outras classes que também herdam a classe Duck, diferenciando-se apenas em seus comportamentos de voar e grasnar, que não é o mesmo para todos os patos. Na implementação da classe MallardDuck, verifica-se que, em seu construtor são instanciados os dois tipos de comportamento para este pato especificamente. Como trata-se de um pato que voa e que também grasna, o seu atributo quackBehaviour recebe a instância da classe Quack() e o seu atributo flyBehaviour recebe a instância da classe FlyWithWings. Padrão Strategy 1 2 3 191 public abstract class Duck { FlyBehaviour flyBehaviour; QuackBehaviour quackBehaviour; 4 public abstract void display(); 5 6 public void performFly(){ flyBehaviour.fly(); } 7 8 9 10 public void performQuack(){ quackBehaviour.quack(); } 11 12 13 14 public void swim(){ System.out.println("All ducks float, even decoys!"); } 15 16 17 18 } Código 5.10.1: Classe Duck em Java 1 2 -class(duck). -export([display/0, performFly/0, performQuack/0, swim/0]). 3 4 attributes. 5 6 7 FlyBehaviour; QuackBehaviour. 8 9 methods. 10 11 display() -> null. 12 13 14 15 performFly() -> Temp1 = self::FlyBehaviour, Temp1::fly(). 16 17 18 19 performQuack() -> Temp2 = self::QuackBehaviour, Temp2::quack(). 20 21 22 swim() -> io:format("All ducks float, even Decoys!~n"). Código 5.10.2: Classe Duck em ooErlang Desta forma, sempre que forem utilizados os métodos PerformFly() e PerformQuack() deste pato, o comportamento realizado será o definido nas respectivas classes. A classe DecoyDuck, por exemplo, em sua instanciação, recebe como comportamentos as instâncias das classes MuteQuack e FlyNoWay. Como trata-se de um pato de decoração, Padrão Strategy 1 2 3 4 5 192 public class MallardDuck extends Duck { public MallardDuck(){ quackBehaviour = new Quack(); flyBehaviour = new FlyWithWings(); } 6 public void display(){ System.out.println("I’m a real Mallard Duck!"); } 7 8 9 10 } Código 5.10.3: Classe MallardDuck em Java 1 2 3 4 -class(mallardDuck). -extends(duck). -export([new/0, display/0]). -constructor([new/0]). 5 6 methods. 7 8 9 10 new() -> self::QuackBehaviour = quack::new_(), self::FlyBehaviour = flyWithWings::new_(). 11 12 13 display() -> io:format("I’m a real Mallard Duck!~n"). Código 5.10.4: Classe MallardDuck em ooErlang é lógico que este pato não é capaz de voar nem de grasnar. Percebe-se, dessa forma, que cada interface de comportamento é uma famı́lia de algoritmos, pois define diferentes reações para cada comportamento. Ao ser observada a implementação da classe FlyWithWings, mostrada nos códigos 5.10.5 e 5.10.6 (Java e ooErlang respectivamente), observa-se o comportamento de vôo desta classe. 1 2 3 4 5 public class FlyWithWings implements FlyBehaviour{ public void fly(){ System.out.println("I’m flying!!"); } } Código 5.10.5: Classe FlyWithWings em Java Para todos os patos que utilizarem o comportamento de vôo FlyBehaviour, o simulador de patos mostra uma mensagem confirmando que o pato é capaz de voar. Nos outros Padrão Strategy 1 2 3 193 -class(flyWithWings). -implements(flyBehaviour). -export([fly/0]). 4 5 methods. 6 7 8 fly() -> io:format("I’m flying!!~n"). Código 5.10.6: Classe FlyWithWings em ooErlang comportamentos relacionados à ação de voar, assim como nos comportamentos relacionados ao grasnar, todas as classes que implementam as respectivas interfaces implementam o método fly (da interface FlyBehaviour) e o método quack() (da interface QuackBehaviour). Estes métodos são invocados dentro dos métodos PerformFly() e PerformQuack(), respectivamente, e que estão definidos na classe Duck. Ao serem implementados todos os tipos de comportamento para as ações de voar e de grasnar dos patos, é possivel instanciar os comportamentos próprios de cada pato, de acordo com as classes que implementam as interfaces FlyBehaviour e QuackBehaviour. A classe principal MiniDuckSimulator é responsável por testar a implementação realizada do padrão de projetos Strategy, instanciando diferentes patos e invocando os métodos referentes às ações de voar e grasnar. Os códigos mostrados em 5.10.7 e 5.10.8 mostram as implementações em Java e ooErlang da classe MiniDuckSimulator. O método principal (main) da classe MiniDuckSimulator realiza testes nos quatro (4) tipos diferentes de patos que foram definidos para este exemplo. É instanciado um objeto para cada um dos patos. Em seguida, para cada pato é chamado o método display(), que tem por objetivo apenas apresentar o pato em questão. Após isto, são chamados os métodos performQuack() e performFly() nesta ordem. A chamada destes métodos está definida na classe Duck, herdada por todas as classes que representam os diferentes tipos de patos. Ao ser chamado o método PerformQuack(), o método a ser executado é o método da classe que foi instanciada e armazenada no atributo de cada pato, no instante da criação dos patos. O comportamento de voar e grasnar dos patos é definido nas classes que herdam a classe Duck, mais especificamente no construtor. Desta forma, cada pato passa a possuir comportamentos próprios para a ação de voar e de grasnar. O método relacionado ao nadar Padrão Strategy 1 194 public class MiniDuckSimulator { 2 public static void main(String[] args) { Duck mallard = new MallardDuck(); mallard.display(); mallard.performQuack(); mallard.performFly(); mallard.swim(); 3 4 5 6 7 8 9 Duck redhead = new RedheadDuck(); redhead.display(); redhead.performQuack(); redhead.performFly(); redhead.swim(); 10 11 12 13 14 15 Duck rubber = new RubberDuck(); rubber.display(); rubber.performQuack(); rubber.performFly(); rubber.swim(); 16 17 18 19 20 21 Duck decoy = new DecoyDuck(); decoy.display(); decoy.performQuack(); decoy.performFly(); decoy.swim(); 22 23 24 25 26 } 27 28 } Código 5.10.7: Classe principal MiniDuckSimulator em Java do pato é o mesmo para cada um e está definido na classe Duck. As figuras 5.26 e 5.27 mostram as execuções deste exemplo em Java e ooErlang, respectivamente. O conceito principal a ser observado na utilização do padrão de projetos Strategy é o de verificar em uma modelagem quais são os aspectos que podem modificar dos aspectos que modificam-se com mais dificuldade, ou que permanecem sem modificações. No exemplo utilizado, os comportamentos de vôo dos patos é o aspecto sujeito a modificações, pois, caso seja necessário, é possı́vel que um pato modifique algum de seus comportamentos. Inclusive é possivel criar métodos que possibilitem esta mudança em tempo de execução. A facilidade na extensão e na manutenção de certo sistema torna-se notável quando o padrão Strategy é utilizado. Tendo-se como exemplo o mini simulador de patos, verificase que os patos são desacoplados de seus comportamentos. Dessa forma, se for necessário inserir novas ações para cada um dos comportamentos definidos, é necessário apenas inserir novas classes que implementem as referidas interfaces. A retirada de comportamentos Padrão Strategy 1 2 195 -class(miniDuckSimulator). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 10 11 main() -> Mallard = mallardDuck::new(), Mallard::display(), Mallard::performQuack(), Mallard::performFly(), Mallard::swim(), 12 13 14 15 16 17 Redhead = redheadDuck::new(), Redhead::display(), Redhead::performQuack(), Redhead::performFly(), Redhead::swim(), 18 19 20 21 22 23 Rubber = rubberDuck::new(), Rubber::display(), Rubber::performQuack(), Rubber::performFly(), Rubber::swim(), 24 25 26 27 28 29 Decoy = decoyDuck::new(), Decoy::display(), Decoy::performQuack(), Decoy::performFly(), Decoy::swim(). Código 5.10.8: Classe principal MiniDuckSimulator em ooErlang também não gera grandes impactos ao sistema como um todo. E se um comportamento for modificado, os patos que o possuı́rem são automaticamente atualizados. As famı́lias de algoritmos neste padrão, referentes ao exemplo utilizado são os diferentes tipos de comportamento para cada uma das duas ações definidas para os patos (voar e grasnar). Estas famı́lias possuem pequenas diferenças, e podem ser utilizadas independentemente, assim como podem ser reutilizadas, dependendo de cada caso especı́fico. Para um mesmo comportamento existem diferentes implementações de algoritmos. Um detalhe a ser verificado é o de que a utilização deste padrão pode aumentar significativamente a quantidade de objetos, dependendo da quantidade de algoritmos implementados. Padrão Template Method 196 Figura 5.26: Execução do exemplo para o padrão Strategy em Java. 5.11 Padrão Template Method O padrão de projetos Template Method, tem por objetivo principal, de acordo com sua definição: “Definir o esqueleto de um algoritmo dentro de uma operação, deixando alguns passos a serem preenchidos pelas subclasses. Template Method permite que suas subclasses redefinam certos passos de um algoritmo sem mudar sua estrutura” [Gamma et al., 1994]. Em situações onde um método de uma classe possa ser utilizado como base para criar variações de algoritmos, utilizando-se de uma estrutura em comum, o padrão Template Method pode ser utilizado. Observa-se então que, para que este padrão possa ser aplicado, é necessário utilizar um método que esteja dividido em duas partes: uma parte invariável, referente à estrutura principal do método, utilizada da mesma forma em todas as classes que estenderem a classe a qual este método está definido e uma parte variável. Esta parte variável é o algoritmo Padrão Template Method 197 Figura 5.27: Execução do exemplo para o padrão Strategy em ooErlang. implementado por subclasses, que modifica algumas partes do método mas sua estrutura principal não é alterada. Assim podem ser criadas diferentes variações de comportamento utilizando-se uma mesma estrutura principal. 5.11.1 Exemplo utilizado para este padrão Para que este padrão de projetos possa ser demonstrado por meio de um exemplo prático, foi utilizada uma implementação retirada de [Freeman et al., 2004] na qual é implementada uma loja de bebidas à base de cafeı́na. As duas principais bebidas modeladas neste exemplo são café e chá. Para que se possa preparar estas bebidas, são realizados diferentes passos, porém alguns desses passos mostram-se semelhantes. Neste caso, é possı́vel utilizar o conceito do padrão Template Method e criar um método que funcione como base para criação de diferentes bebidas. Na modelagem do exemplo que utiliza o padrão Template Method são criadas duas classes que possuem o método utilizado como estrutura de criação: a classe CaffeineBeverage Padrão Template Method 198 e a classe CaffeineBeverageWithHook. Para cada uma dessas classes, duas subclasses a estendem, sendo que uma é voltada para a criação do café (Coffee) e outra para a criação do chá (Tea). Para a classe CaffeineBeverageWithHook também existem duas classes para a criação das bebidas, sendo elas: CoffeeWithHook e TeaWithHook. A figura 5.28 mostra o diagrama de classes para este exemplo. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Figura 5.28: Diagrama de classes do exemplo para o padrão TemplateMethod. A classe BeverageTestDrive é a classe principal, responsável por instanciar os objetos e realizar os testes da modelagem deste exemplo. É importante observar que, tanto a classe CaffeineBeverage quanto a classe CaffeineBeverageWithHook são responsáveis por criar as bebidas café e chá. A diferença entre elas é de que em CaffeineBeverageWithHook existe um método a mais, chamado customerWantsCondiments(), pelo qual é possı́vel inserir ou não condimentos nas bebidas, de acordo com a vontade do usuário. Na classe CaffeineBeverage os condimentos são inseridos automaticamente. Padrão Template Method 5.11.2 199 Comparação das implementações em Java e ooErlang Para que se tenha a aplicação do padrão Template Method é necessária a modelagem de um método que permita às subclasses, que estendem a classe à qual este método pertence, criar pequenas variações deste método, modificando algumas partes e outras não. No exemplo utilizado, o método “modelo ” chama-se prepareRecipe() e está definido em duas classes, CaffeineBeverage e CaffeineBeverageWithHook. Os códigos mostrados em 5.11.1 e 5.11.2 mostram a implementação em Java e ooErlang da classe CaffeineBeverage. 1 public abstract class CaffeineBeverage { 2 final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } 3 4 5 6 7 8 9 abstract void brew(); 10 11 abstract void addCondiments(); 12 13 void boilWater() { System.out.println("Boiling water"); } 14 15 16 17 void pourInCup() { System.out.println("Pouring into cup"); } 18 19 20 21 } Código 5.11.1: Classe CaffeineBeverage em Java O método prepareRecipe() é responsável por preparar tanto café quanto chá. Quatro (4) métodos são chamados dentro deste método: BoilWater() (ferver água), brew() (preparos iniciais da bebida), pourInCup() (armazenar bebida em recipiente) e addCondiments() (adicionar condimentos). Se o processo for observado, é percebido que o primeiro e o terceiro métodos são semelhantes para ambas as bebidas e, portanto, já estão implementados na própria classe CaffeineBeverage. Já o segundo e o quarto métodos são diferentes para cada bebida. Para os métodos que precisam de uma implementação própria, as subclasses que estendem a classe principal implementam estes métodos de acordo com suas caracterı́sticas. Em Padrão Template Method 1 2 3 200 -class(caffeineBeverage). -export([prepare_recipe/1, brew/0, add_condiments/0]). -export([boil_water/0, pour_in_cup/0]). 4 5 methods. 6 7 8 9 10 11 12 prepare_recipe(Class) -> Object = {Class, ObjectID}, Object::boil_water(), Object::brew(), Object::pour_in_cup(), Object::add_condiments(). 13 14 brew() -> null. 15 16 add_condiments() -> null. 17 18 19 boil_water() -> io:format("Boiling water~n"). 20 21 22 pour_in_cup() -> io:format("Pouring into cup~n"). Código 5.11.2: Classe CaffeineBeverage em ooErlang virtude disto, os métodos brew() e addCondiments() não estão definidos diretamente na classe CaffeineBeverage. Os métodos definidos diretamente na superclasse fazem parte da estrutura principal do método modelo prepareRecipe(). Os outros representam as variações nos algoritmos que diferenciam as bebidas. O método brew() (preparos iniciais) para o preparo de um café refere-se à filtragem do café recém-fervido. Já para o preparo do chá, refere-se ao processo de deixar o chá ficar impregnado com o sabor da erva utilizada. Por serem dois métodos que possuem objetivos especı́ficos e voltados para o tipo de objeto que irão construir, devem ser implementados nas subclasses que estendem a classe CaffeineBeverage. Devido a isso, esses métodos são abstratos na superclasse. Os códigos mostrados em 5.11.3 e 5.11.4 mostram a implementação da classe Coffee em Java e ooErlang respectivamente. Conforme esperado, os métodos especı́ficos de cada bebida são implementados nas classes Coffee e Tea. Esses métodos são brew() e addCondiments(). Assim como o processo inicial de preparo do café é diferente do processo de preparo do chá, os condimentos que o café recebe são também distintos dos condimentos recebidos pelo chá. Para o café os condimentos disponı́veis são açúcar e leite. Para o chá o condimento é apenas limão. Dessa Padrão Template Method 1 2 3 4 5 6 7 8 201 public class Coffee extends CaffeineBeverage { public void brew() { System.out.println("Dripping Coffee through filter"); } public void addCondiments() { System.out.println("Adding Sugar and Milk"); } } Código 5.11.3: Classe Coffee em Java 1 2 3 -class(coffee). -extends(caffeineBeverage). -export([brew/0, add_condiments/0]). 4 5 methods. 6 7 8 brew() -> io:format("Dripping Coffee through filter~n"). 9 10 11 add_condiments() -> io:format("Adding Sugar and Milk~n"). Código 5.11.4: Classe Coffee em ooErlang forma, apesar de ambas as bebidas estenderem a classe CaffeineBeverage, em apenas alguns dos métodos o comportamento é distinto. Na classe CaffeineBeverageWithHook é utilizado um método extra, para que possa ser dado ao usuário a possibilidade de escolha, se ele quer ou não que sua bebida tenha condimentos, seja ela café ou chá. No método prepareRecipe() da classe CaffeineBeverageWithHook o método addCondiments() é chamado somente se o método customerWantsCondiments() retornar valor verdadeiro. E este método não é abstrato na superclasse, mas é implementado nas subclasses CoffeeWithHook e TeaWithHook. Os códigos apresentados em 5.11.5 e 5.11.6 mostram a implementação da classe CoffeeWithHook em Java e ooErlang, nesta ordem. O método customerWantsCondiments() presente na classe CaffeineBeverageWithHook não é um método abstrato, mas é implementado para retornar apenas um valor padrão. Isto é feito desta maneira para que as subclasses possam ser livres para implementar ou não o método. Portanto esta estratégia de implementação é chamada de “hook” (gancho, em inglês), pois a subclasse tem a possibilidade de poder decidir se quer ou não sobrescrever Padrão Template Method 1 202 public class CoffeeWithHook extends CaffeineBeverageWithHook { 2 public void brew() { System.out.println("Dripping Coffee through filter"); } 3 4 5 6 public void addCondiments() { System.out.println("Adding Sugar and Milk"); } 7 8 9 10 public boolean customerWantsCondiments() { 11 12 String answer = getUserInput(); 13 14 if (answer.toLowerCase().startsWith("y")) { return true; } else { return false; } 15 16 17 18 19 } 20 21 private String getUserInput() { String answer = null; 22 23 24 System.out.print("Would you like milk and sugar with your coffee (y/n)? "); 25 26 27 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { answer = in.readLine(); } catch (IOException ioe) { System.err.println("IO error trying to read your answer"); } if (answer == null) { return "no"; } return answer; 28 29 30 31 32 33 34 35 36 37 38 } 39 40 } Código 5.11.5: Classe CoffeeWithHook em Java este método. Se o método não for sobrescrito, continuará retornando um valor padrão. Caso seja sobrescrito, retornará o comportamento implementado nas referidas subclasses. Para que as implementações do exemplo para o padrão Template Method possam ser testadas, é utilizada a classe BeverageTestDrive com seu método principal (main). Deve-se levar em consideração o fato de que não foi implementado, neste exemplo, somente um método com o objetivo de estruturar outros comportamentos. Duas foram, de fato, as classes Padrão Template Method 1 2 3 203 -class(coffeeWithHook). -extends(caffeineBeverageWithHook). -export([brew/0, add_condiments/0, customer_wants_condiments/0]). 4 5 methods. 6 7 8 brew() -> io:format("Dripping Coffee through filter~n"). 9 10 11 add_condiments() -> io:format("Adding Sugar and Milk~n"). 12 13 14 15 16 17 18 19 20 customer_wants_condiments() -> Answer = get_user_input(), if (Answer == y) -> true; true -> false end. 21 22 23 24 25 get_user_input() -> {ok, Answer} = io:read(’Would you like milk and sugar with your coffee(y/n)?’), Answer. Código 5.11.6: Classe CoffeeWithHook em ooErlang modeladas contendo o método principal para a aplicação do padrão Template Method: CaffeineBeverage e CaffeineBeverageWithHook. Os códigos apresentados em 5.11.7 e 5.11.8 mostram a implementação em Java e ooErlang da classe BeverageTestDrive. Para realizar os testes na modelagem do exemplo para o padrão Template Method, inicialmente são instanciadas duas classes, cada uma referente a um tipo de bebida, Coffee e Tea. Em seguida o objeto da classe Tea invoca o método prepareRecipe(). Este é o método utilizado como molde para criação de variações de algoritmos, conforme define o padrão TemplateMethod. Ao ser invocado, este método executa outros quatro métodos que estão presentes nele, sendo que, em dois deles, a implementação encontra-se na própria classe CaffeineBeverage, e em outros dois encontra-se na subclasse Tea. O mesmo procedimento é realizado também para a instância da classe Coffee. Ao ser chamado o método prepareRecipe(), todos os métodos necessários para que a bebida seja preparada são executados. O resultado é mostrado na saı́da, com a impressão de mensagens para cada método executado que esteja preparando a bebida. Em seguida são instanciados os objetos das classes CoffeeWithHook e TeaWithHook. Neste teste verifica-se a execução Padrão Template Method 1 2 204 public class BeverageTestDrive { public static void main(String[] args) { 3 Tea tea = new Tea(); Coffee coffee = new Coffee(); 4 5 6 System.out.println("\nMaking tea..."); tea.prepareRecipe(); 7 8 9 System.out.println("\nMaking coffee..."); coffee.prepareRecipe(); 10 11 12 13 TeaWithHook teaHook = new TeaWithHook(); CoffeeWithHook coffeeHook = new CoffeeWithHook(); 14 15 16 System.out.println("\nMaking tea..."); teaHook.prepareRecipe(); 17 18 19 System.out.println("\nMaking coffee..."); coffeeHook.prepareRecipe(); 20 21 } 22 23 } Código 5.11.7: Classe principal BeverageTestDrive em Java 1 2 -class(beverageTestDrive). -export([main/0]). 3 4 class_methods. 5 6 7 8 main() -> Tea = tea::new_(), Coffee = coffee::new_(), 9 10 11 io:format("~nMaking Tea...~n"), Tea::prepare_recipe(tea), 12 13 14 io:format("~nMaking Coffee...~n"), Coffee::prepare_recipe(coffee), 15 16 17 TeaHook = teaWithHook::new_(), CoffeeHook = coffeeWithHook::new_(), 18 19 20 io:format("~nMaking Tea...~n"), TeaHook::prepare_recipe(teaWithHook), 21 22 23 io:format("~nMaking Coffee...~n"), CoffeeHook::prepare_recipe(coffeeWithHook). Código 5.11.8: Classe principal BeverageTestDrive em ooErlang Padrão Template Method 205 de forma semelhante com a realizada nas classes Coffee e Tea, com a diferença de que o usuário pode escolher se quer ou não condimentos em sua bebida. As figuras mostradas em 5.29 e 5.30 mostram as execuções do padrão de projetos Template Method nas linguagens Java e ooErlang, respectivamente. Verifica-se que, na execução do método prepareRecipe() quando é invocado pela instância das classes CoffeeWithHook e TeaWithHook, o sistema fica em modo de espera até que o usuário entre com as informações dizendo se aceita ou não condimentos em sua bebida. Este comportamento é verificado tanto na implementação em Java, quanto em ooErlang. Figura 5.29: Execução do exemplo para o padrão Template Method em Java. A implementação e os testes do exemplo para o padrão de projetos Template Method na extensão ooErlang mostrou resultados semelhantes aos encontrados na execução da implementação em Java. Verifica-se, desta forma, que a extensão ooErlang possui ferramentas que tornam possivel o suporte na utilização de métodos que funcionam como moldes para Padrão Visitor 206 Figura 5.30: Execução do exemplo para o padrão Template Method em ooErlang. que se possa utilizar diferentes algoritmos com uma mesma estrutura. Este é o conceito do padrão Template Method, visando a simplificação do sistema referente à extensão e manutenção do mesmo. Este padrão de projetos fundamenta-se essencialmente na reutilização de códigos. É importante ser utilizado para implementar as estruturas de um objeto que não se modificam, deixando a responsabilidade de construir o que pode variar para as subclasses. Outro motivo para se utilizar deste padrão é quando certa duplicação de códigos deve ser evitada. No exemplo utilizado, se não houvesse a modelagem do método “modelo”, haveriam duas classes distintas (café e chá), e os métodos iguais seriam duplicados, gerando maiores possibilidades de inconsistência e erros do sistema. 5.12 Padrão Visitor Levando-se em consideração a definição do padrão Visitor, observa-se que o objetivo principal deste padrão de projetos é: “Representar uma operação a ser realizada sobre os elementos de uma estrutura de objetos. Visitor permite definir uma nova operação sem Padrão Visitor 207 mudar as classes dos elementos nos quais opera” [Gamma et al., 1994]. Desta forma, o padrão de projetos Visitor é utilizado para que seja possı́vel obter mais informações a respeito de uma composição de objetos, sem que seja preciso modificar a estrutura desta composição. Uma das situações em que é possı́vel aplicar o padrão Visitor é quando um sistema possui uma estrutura de objetos que, por sua vez, possui diferentes interfaces, sendo necessário realizar operações nesses objetos que dependem das suas classes concretas e não somente das interfaces. O padrão Visitor permite que seja possivel realizar operações em cada uma das classes de uma estrutura de objetos sem que seja necessario modificar o código fonte existente. A mudança é feita na classe “visitante”, percorrendo cada elemento da estrutura para realizar a devida operação. 5.12.1 Exemplo utilizado para este padrão Para ilustrar a utilização do padrão de projetos Visitor, foi utilizado um exemplo retirado de [Books, 2013] em que é modelado um sistema que possui um conjunto de objetos. Esse conjunto de objetos faz parte de alguns dos elementos presentes em um veı́culo automotor. Para cada um destes objetos são realizadas duas ações, visitar e interagir com o elemento. Para cada uma destas ações foi utilizada uma classe “visitante”, responsável por interagir com todos os elementos da referida agregação. Na modelagem deste exemplo foram definidas duas interfaces, CarElement e CarElementVisitor. A interface CarElement é implementada por todos os objetos da agregação que também são elementos pertencentes ao veı́culo. Já a interface CarElementVisitor é implementada pelas duas classes responsáveis por visitar e interagir com cada objeto que representa os elementos do veı́culo. Os elementos do veı́culo são representados pelas classes Body (lataria), Wheel (roda) e Engine (motor). A figura 5.31 mostra o diagrama de classes deste exemplo. As classes sobrepostas possuem implementação similar às classes que as sobrepõem. Verifica-se que a interface CarElement é implementada pelas três classes que representam as partes principais de um veı́culo automotor e que a interface CarElementVisitor é implementada pelas classes que visitam cada um dos elementos do veı́culo, reealizando as Padrão Visitor 208 Figura 5.31: Diagrama de classes do exemplo para o padrão Visitor. tarefas necessárias em cada um. A classe CarElementPrintVisitor ao visitar cada elemento do veı́culo retorna uma mensagem confirmando que o visitou. A classe CarElementDoVisitor interage com todos os elementos do veı́culo que visita. A classe principal deste exemplo, que possui o método main() chama-se VisitorExample. 5.12.2 Comparação das implementações em Java e ooErlang A agregação dos elementos que armazenam os objetos referentes às principais partes de um veı́culo estão armazenadas na classe Car. Esta classe é responsável por possuir os dados do veı́culo que devem ser trabalhados pelas classes visitantes. Além de possuir os elementos do veı́culo, a própria classe Car é também possivel de ser visitada pelas instâncias das classes que implementam a interface CarElementVisitor. Os códigos mostrados em 5.12.1 Padrão Visitor 209 e 5.12.2 mostram as implementações da classe Car em Java e ooErlang, nesta ordem. 1 2 public class Car implements CarElement { CarElement[] elements; 3 public Car() { this.elements = new CarElement[] { new new new new new } 4 5 6 7 8 9 10 Wheel("front left"), Wheel("front right"), Wheel("back left"), Wheel("back right"), Body(),new Engine()}; 11 public void accept(CarElementVisitor visitor) { for (CarElement elem : elements) { elem.accept(visitor); } visitor.visit(this); } 12 13 14 15 16 17 18 } Código 5.12.1: Classe Car em Java 1 2 3 4 -class(car). -implements(carElement). -export([new/0, accept/1]). -constructor([new/0]). 5 6 attributes. 7 8 Elements. 9 10 methods. 11 12 13 14 15 new() -> self::Elements = [wheel::new("front left"), wheel::new("front right"), wheel::new("back left"), wheel::new("back right"), body::new_(), engine::new_()]. 16 17 18 19 20 accept(Visitor) -> SelfObject = {car, ObjectID}, aux_accept(self::Elements, Visitor), Visitor::visit(SelfObject). Código 5.12.2: Classe Car em ooErlang Conforme pode ser observado nas implementações da classe Car, esta classe possui apenas um atributo chamado elements. Este atributo é uma lista que armazena objetos das classes que implementam a interface CarElement, ou seja, são os elementos do veı́culo. O Padrão Visitor 210 construtor da classe Car instancia quatro objetos da classe Wheel (pois um carro geralmente possui quatro rodas), e uma instância das classes Body e Engine. Esta classe ainda possui um método chamado accept() pelo qual permite-se o acesso à classe visitante para todos os elementos do veı́culo. As classes que implementam a interface CarElement devem possuir o também o método accept(), pois este é o único método abstrato desta interface. Uma das caracterı́sticas principais do padrão Visitor é que, com a utilização de uma classe visitante, o encapsulamento das classes pertencentes à agregação e que precisam ser visitadas pode ser violado. Isto é realizado por meio de um método inserido nas próprias classes visitadas para permitir acesso. Neste caso este método é o accept(). Os códigos mostrados em 5.12.3 e 5.12.4 apresentam as implementações em Java e ooErlang da classe Wheel. 1 2 public class Wheel implements CarElement { private String name; 3 public Wheel(String name) { this.name = name; } 4 5 6 7 public String getName() { return this.name; } 8 9 10 11 public void accept(CarElementVisitor visitor) { visitor.visit(this); } 12 13 14 15 } Código 5.12.3: Classe Wheel em Java Todas as classes que implementam a interface CarElement possuem o método accept() para permitir que o objeto “visitante” tenha acesso ao objeto visitado. Mas a classe Wheel é a única que, além de possuir este método, ainda possui um atributo chamado name, um construtor e o método getName(). Isto ocorre pois o veı́culo automotor do exemplo possui quatro rodas e elas são diferenciadas por seus nomes. o método getName() é responsável apenas por retornar o nome (neste caso o nome é apenas a descrição) da roda. As duas classes visitantes, CarElementPrintVisitor e CarElementDoVisitor possuem implementações semelhantes, mas não idênticas. A forma com a qual suas instâncias visitam os elementos do veı́culo é a mesma. O que diferencia essas classes visitantes é a Padrão Visitor 1 2 3 4 211 -class(wheel). -implements(carElement). -export([new/1, get_name/0, accept/1]). -constructor([new/1]). 5 6 attributes. 7 8 Name. 9 10 methods. 11 12 13 new(Name) -> self::Name = Name. 14 15 16 get_name() -> self::Name. 17 18 19 20 accept(Visitor) -> SelfObject = {wheel, ObjectID}, Visitor::visit(SelfObject). Código 5.12.4: Classe Wheel em ooErlang operação realizada por cada uma ao visitar os elementos do veı́culo. Enquanto que a classe CarElementPrintVisitor apenas imprime na tela que está visitando um elemento, a classe CarElementDoVisitor realiza uma interação com os objetos visitados. Os códigos mostrados em 5.12.5 e 5.12.6 apresentam as implementações da classe CarElementDoVisitor em Java e ooErlang. 1 2 3 4 public class CarElementDoVisitor implements CarElementVisitor { public void visit(Wheel wheel) { System.out.println("Kicking my " + wheel.getName() + " wheel"); } 5 public void visit(Engine engine) { System.out.println("Starting my engine"); } 6 7 8 9 public void visit(Body body) { System.out.println("Moving my body"); } 10 11 12 13 public void visit(Car car) { System.out.println("Starting my car"); } 14 15 16 17 } Código 5.12.5: Classe CarElementDoVisitor em Java Padrão Visitor 1 2 3 212 -class(carElementDoVisitor). -implements(carElementVisitor). -export([visit/1]). 4 5 methods. 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 visit(ObjectVisited) -> {Class,_} = ObjectVisited, if (Class == wheel) -> io:format("Kicking my ~p wheel~n", [ObjectVisited::get_name()]); (Class == engine) -> io:format("Starting my engine~n"); (Class == body) -> io:format("Moving my body~n"); (Class == car) -> io:format("Starting my car~n"); true -> ok end. Código 5.12.6: Classe CarElementDoVisitor em ooErlang A classe CarElementDoVisitor implementa os quatro métodos em que realiza a visita aos elementos do veı́culo. Um método para cada um dos elementos visitados. Os métodos, ao serem implementados em Java, podem possuir os nomes e a quantidade de atributos semelhante, contanto que o tipo de atributo de entrada seja diferente. Já em ooErlang, que herda as caracterı́sticas de implementação do Erlang, não pode possuir dois métodos com o mesmo nome e quantidade de parâmetros, pois esta extensão apenas trabalha com os parâmetros, sem verificar de que tipo são. Desta forma utilizou-se uma estrutura IF para implementar o método visit() em ooErlang. A idéia central do método visit() da classe CarElementDoVisitor é semelhante, tanto na implementação em Java quanto na implementação em ooErlang. A única diferença entre essas implementações é que em Java foram criadas quatro classes para visitar os quatro elementos do veı́culo, enquanto que em ooErlang criou-se apenas uma classe. As quatro classes visitadas são as instâncias de Wheel, Body, Engine e a própria classe Car. A classe principal VisitorExample é mostrada nos códigos em 5.12.7 e 5.12.8 (Java e ooErlang respectivamente). O método principal da classe VisitorExample (main) realiza o teste de utilização das classes visitantes de forma sucinta. Inicialmente é instanciado um objeto da classe Car. Padrão Visitor 1 2 3 4 5 6 7 213 public class VisitorExample { public static void main(String[] args) { CarElement car = new Car(); car.accept(new CarElementPrintVisitor()); car.accept(new CarElementDoVisitor()); } } Código 5.12.7: Classe principal VisitorExample em Java 1 2 -class(visitorExample). -export([main/0]). 3 4 class_methods. 5 6 7 8 9 main() -> Car = car::new(), Car::accept(carElementPrintVisitor::new_()), Car::accept(carElementDoVisitor::new_()). Código 5.12.8: Classe principal VisitorExample em ooErlang Quando isto é feito, automaticamente esta classe também instancia todos os elementos do veı́culo a serem visitados e os armazena em seu atributo chamado elements. Em seguida, na classe VisitorExample, é chamado o método accept() duas vezes, para que a instância da classe Car permita o acesso dos dois visitantes aos elementos do veı́culo automotor. O método accept() utilizado na classe VisitorExample recebe como parâmetro de entrada as instâncias das duas classes responsáveis por realizar a visita a cada um dos elementos do veı́culo. Dentro do método accept(), definido na classe Car, é chamado o método visit() que realiza a visita a cada elemento pertencente à lista elements, da classe Car. Dessa forma, todos os elementos são visitados e as operações de cada visitante é realizada em cada um deles. As figuras mostradas em 5.32 e 5.33 mostram a execução deste exemplo em Java e ooErlang, nesta ordem. Ao utilizar o padrão de projetos Visitor, é possı́vel utilizar todos os diferentes elementos de uma agregação para realizar uma operação que inicialmente não foi implementada para os elementos da referida operação. No caso do exemplo utilizado, nenhuma das classes que representa os elementos do veı́culo implementa a interação realizada por meio das classes visitantes. Uma das principais vantagens na utilização deste padrão de projetos é a facilidade de trabalhar com as diferentes classes de uma agregação sem ter que modificar Padrão Visitor 214 Figura 5.32: Execução do exemplo para o padrão Visitor em Java. o código fonte já existente nos elementos da agregação. A extensão da linguagem Erlang que permite suporte à orientação à objetos, ooErlang, ao implementar e executar o exemplo que ilustra o padrão Visitor, mostrou comportamento similar ao comportamento apresentado pela implementação e execução da linguagem Java. Dessa forma, esta extensão do Erlang pode ser utilizada em situações similares, em que seja necessário acessar diversos elementos de uma agregação realizando operações e tarefas que originalmente não foram implementadas nos objetos da agregação. Ao ser utilizado o padrão de projetos Visitor, torna-se mais fácil inserir novas operações a serem realizadas nos objetos visitados. Sem a utilização deste padrão, seria necessário modificar cada um dos objetos envolvidos na operação. Ao utilizar o padrão Visitor, a operação é definida na própria classe visitante. Uma consequência negativa ao utilizar este padrão é a possivel quebra do encapsulamento das classes visitadas, por terem que permitir Conclusões dos Padrões de Projeto Comportamentais em ooErlang 215 Figura 5.33: Execução do exemplo para o padrão Visitor em ooErlang. o acesso do objeto visitante. 5.13 Conclusões dos Padrões de Projeto Comportamentais em ooErlang Conforme os exemplos dos padrões de projeto comportamentais implementados, observa-se que o foco principal destes padrões são os algoritmos das classes e a passagens de responsabilidade entre os objetos. A comunicação entre os objetos, além dos objetos e classes em si, é um dos principais pontos levados em consideração ao serem implementados os padrões comportamentais. A herança é muitas vezes utilizada para distribuir responsabilidades entre subclasses. Para as implementações realizadas em ooErlang, em todos os exemplos a quantidade de classes implementadas corresponde à quantidade existente nas implementações em Java. A proximidade da sintaxe e da estrutura de um fonte em ooErlang em relação aos correspondentes fontes em Java mostra ser um dos fatores existentes que facilitam o trabalho ao Conclusões dos Padrões de Projeto Comportamentais em ooErlang 216 se escrever um fonte em ooErlang, principalmente quando se tem um fonte correspondente em Java que, em teoria, deve executar de forma similar. Os onze (11) exemplos implementados dos padrões de projeto comportamentais em ooErlang ao serem compilados e testados, apresentam como resultado uma execução similar ao obtido nas execuções dos fontes compilados em Java. Verifica-se, assim, que os comportamentos para os exemplos desenvolvidos em ooErlang dos padrões comportamentais são os mesmos mostrados em Java e, dessa forma, pode-se afirmar que o ooErlang torna-se uma extensão do Erlang com capacidade para implementar os elementos essenciais dos padrões de projeto comportamentais em modelagens orientadas à objeto. Capı́tulo 6 Conclusão O resultado do presente trabalho permite concluir que a extensão do Erlang para orientação à objetos, ooErlang, possui suporte para a implementação elementar dos padrões de projetos descritos inicialmente pelos autores do livro “Design Patterns: Elements of Reusable Object-Oriented Software”, cuja referencia é citada em [Gamma et al., 1994]. Este livro é um dos primeiros documentos que trata dos vinte e três (23) principais padrões de projetos descobertos e documentados pelos autores conhecidos como a “Gang of Four” (gangue dos quatro, em inglês), ou “GoF”. A ordem na qual os padrões de projetos apresentados neste trabalho são mostrados e explicados é a mesma ordem utilizada pelos autores do livro [Gamma et al., 1994]. Assim é possı́vel explicar separadamente cada tipo de padrão de projeto, tratando em conjunto os padrões de Criação, Estruturais e Comportamentais. Desta forma o entendimento do funcionamento de cada padrão, assim como a aplicação do referido padrão na situação explicada torna-se mais fácil de ser assimilado. A extensão ooErlang foi desenvolvida utilizando-se do compilador original do Erlang. A linguagem de programação Erlang é do tipo código-aberto, o que permite a seus desenvolvedores a modificarem. Desta forma, o compilador do ooErlang foi trabalhado para poder entender novas palavras-chaves referentes à utilização do Erlang na orientacão à objetos. Assim, palavras como “class”, “interface”, “implements” passaram a ser reconhecidas pelo compilador do ooErlang. Além da inclusão dessas novas palavras-chave no compilador do ooErlang, o tratamento 218 de compilação destas novas palavras também foi incluı́do no processo de compilação desta extensão. A compilação de um projeto em ooErlang passa por duas fases bem definidas: análise léxica (no qual as novas palavras-chave são reconhecidas) e análise sintática (onde é construı́da uma árvore sintática do código-fonte em ooErlang. Após a constução da árvore sintática do ooErlang, é feita uma transformação para que esta árvore transforme-se em árvore sintática do Erlang. Essa transformação é o ponto principal da compilação. Esta nova árvore sintática é então utilizada para que seja gerado o código-fonte do ooErlang. Portanto, a transformação da árvore sintática de ooErlang para Erlang permite ao compilador do Erlang gerar os fontes executáveis referentes aos códigos escritos em ooErlang. A extensão ooErlang permite a declaração de classes, utilização de atributos nestas classes, utilização de métodos construtores, criação de interfaces com a assinatura de métodos abstratos, utilização de herança entre classes (dependendo dos diferentes nı́veis de abstração de cada classe), implementação de interfaces por meio de subclasses, criação de métodos públicos e privados, chamada de métodos passando por parâmetro o próprio objeto invocador etc. Com estes elementos do paradigma orientado a objetos, foi possı́vel implementar os padrões de projetos. Cada um dos padrões de projetos desenvolvidos em ooErlang foi utilizado de uma implementação feita originalmente para a linguagem de programação Java. Esta linguagem de programação orientada a objetos foi escolhida por ser uma das linguagens mais utilizada para implementar sistemas com este paradigma. Sendo assim, a linguagem Java torna-se uma boa referência pra que se possa comparar a execução dos padrões implementados em ooErlang, verificando se os resultados das execuções mostram-se semelhantes entre as linguagens. A extensão do Erlang, ooErlang implementa e dá suporte aos principais elementos encontrados no paradigma orientado a objetos, como por exemplo objetos, classes, associação, agregação, composição, herança, polimorfismo, encapsulamento e até mesmo utilização de interfaces. Esta é uma nova linguagem de programação, portanto não é capaz de possuir os mesmos “plugins” de uma linguagem já bastante desenvolvida como o Java. Porém seu compilador, assim como o do Erlang, é código-aberto, permitindo modificações de desenvolvedores externos. Trabalhos Futuros 6.1 219 Trabalhos Futuros O desenvolvimento de uma linguagem de programação, ou de uma extensão à uma linguagem de programação já existente, como o ooErlang, abre oportunidades para diversas pesquisas e trabalhos de desenvolvimento que podem ser realizados com base nesta nova linguagem de programação. A seguir são mostrados alguns trabalhos futuros que podem ser desenvolvidos baseados no presente trabalho: • Teste de desempenho dos Padrões de Projetos: Este trabalho pode ser desenvolvido para verificar o tempo de execução e utilização de memória no ooErlang em comparação com outras linguagem orientadas à objetos, como por exemplo Java, C#, Ruby etc. Para isto podem ser utilizados os mesmos exemplos desenvolvidos assim como novos exemplos podem ser criados. • Desenvolvimento de um sistema de concorrência massiva utilizando ooErlang: Este trabalho tem por objetivo verificar a utilização da extensão ooErlang para criação de um sistema concorrente e massivo, podendo estar relacionado a um sistema de gerenciamento de requisições online, seja em um servidor de jogos ou em um serviço de bate-papo, por exemplo. • Criação de plugin para desenvolvimento em Eclipse, VIM e Emacs: Este pode ser outro campo de estudos do ooErlang, pois, atualmente, a codificação desta linguagem de programação é feita sem a utilização de nenhum editor especı́fico, que reconheça as caracterı́sticas próprias de sintaxe do ooErlang. Assim, seria possivel verificar e corrigir erros sintáticos em tempo de codificação do fonte em ooErlang. Referências Bibliográficas [Armstrong, 1996] Armstrong, J. (1996). Concurrent Programming in Erlang. Prentice Hall, 02nd edition. [Armstrong, 2007] Armstrong, J. (2007). The Pragmatic Programmers. Programming Erlang, 01st edition. [Books, 2013] Books, W. (2013). Wiki Books. Open Books for an Open World. Disponı́vel em <http://en.wikibooks.org/wiki/Computer Science Design Patterns/>. Acessado em Agosto de 2013. [da Silva Júnior, 2013] da Silva Júnior, J. M. (2013). ooErlang - Uma Extensão de Erlang Orientada a Objetos. Ph.D. Dissertation, Universidade Federal de Pernambuco, UFPE, Brasil. [Dall’Oglio, 2009] Dall’Oglio, P. (2009). Introdução à Orientação à Objetos, GOPHP Conference 2009. Disponı́vel em <www.pablo.blog.br/resources/talks/part1-OO2phpgo.pdf>. Acessado em Agosto de 2013. [de Souza Pereira, 2008] de Souza Pereira, A. (2008). Padrões de projeto: Uma compilação dos mais utilizados em projetos de software, Faculdade de Minas. [Deitel and Deitel, 2010] Deitel, H. and Deitel, P. (2010). Java, Como Programar. Prentice Hall, 08th edition. [Eriksson, 2013] Eriksson, D. (2013). AVAJAVA Web Tutorials. Disponı́vel em <http://www.avajava.com/tutorials/lessons/prototype-pattern.html>. Acessado em Agosto de 2013. REFERÊNCIAS BIBLIOGRÁFICAS 221 [Freeman et al., 2004] Freeman, E., Freeman, E., Sierra, K., and Bates, B. (2004). Head First Design Patterns. O’Reilly Media Inc., 01st edition. [Gamma et al., 1994] Gamma, E., Helm, R., Johnson, R., and Vlissides, J. (1994). Design Patterns, Elements of Reusable Object-Oriented Software. Addison-Wesley, 37th edition. [GitHub, 2008] GitHub (2008). GitHub. Build software better, together. Disponı́vel em <https://github.com/jucimarjr/ooerlang/tree/master/examples/headfirst>. Acessado em Agosto de 2013. [Grzesiuk, 2006] Grzesiuk, D. F. (2006). Um estudo de design patterns no desenvolvimento em php 5, Centro de Ensino Superior de Foz do Iguaçu. [Jr. and Lins, 2012] Jr., J. S. and Lins, R. D. (2012). ooErlang: Another Object Oriented Extension to Erlang. Proceedings of the 11th ACM SIGPLAN Workshop on Erlang (Erlang ’12), pp 65-66. ACM, New York. doi: 10.1145/2364489.2364502. [Jr. et al., 2012] Jr., J. S., Lins, R. D., and Aquino, D. H. (2012). ooErlang: Object Oriented Erlang. Applied Computing 2012. Madrid. IADIS Press. [Kulandai, 2012] Kulandai, J. (2012). Java Papers, Java Tutorial Blog. Disponı́vel em <http://javapapers.com/design-patterns/>. Acessado em Agosto de 2013. [Making, 2013] Making, S. (2013). Source Making, Teaching IT Professionals. Disponı́vel em <http://sourcemaking.com/design patterns/>. Acessado em Agosto de 2013. [Martin and Martin, 2006] Martin, R. and Martin, M. (2006). Agile Principles, Patterns and Practices in C#. Prentice Hall, 01st edition. [OSS, 2001] OSS, F. (2001). Protótipo de software para geração de sistemas distribuı́dos utilizando Design Patterns, Universidade Regional de Blumenau. [Truett, 2013] Truett, L. (2013). Java Design Patterns. Disponı́vel <http://www.fluffycat.com/Java-Design-Patterns/>. Acessado em Agosto de 2013. em