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