Os Padrões de Projeto são soluções já encontradas, experimentadas e testadas e que podem ser aplicadas a projetos sem ter que “reinventar a roda”. Vários Padrões de Projeto foram catalogados e são um conjunto de melhores práticas que são seguidos e usados em projetos de software orientado a objetos.
Padrões de Projeto, basicamente, descrevem soluções para problemas recorrentes em sistemas de software orientado a objetos em desenvolvimento.
Além disso, os Padrões de Projeto também definem um vocabulário comum que facilitam o aprendizado e documentação de sistemas de software.
Os padrões de projeto são classificados como:
- Criacional - define a criação de objetos;
- Estrutural - define a composição de classes e objetos;
- Comportamental - define a interação entre classes e objetos.
Neste artigo, vamos ver o padrão de projeto Iterator que é usado em vários projetos e API’s Java.
O Projeto Padrão Iterator visa encapsular uma iteração. O padrão Iterator depende de uma interface chamada Iterator, como pode ser visto abaixo:
Figura 1: Exemplo da interface Iterator
O método hasNext () determina se há mais elementos a serem iterados. O método next retorna o próximo objeto na iteração.
Depois disso, já temos a interface que pode implementar iterações para qualquer coleção de objetos: matrizes, listas, tabelas de hash, etc. Para cada conjunto de objetos que você deseja encapsular a iteração cria uma implementação para a interface Iterator definido acima. Por exemplo, para encapsular uma iteração de um menu teria a classe abaixo:
Figura 2: Exemplo de Implementação de interface Iterator
Um método que poderia ser parte de nosso diagrama é o "excluir" para remover objetos da coleção, no entanto, este é um método opcional, não precisamos necessariamente fornecer recursos para remoção.
O padrão Iterator é definido como: "O padrão Iterator fornece uma maneira de acessar seqüencialmente os elementos de um objeto agregado sem expor sua representação subjacente." Portanto, o padrão Iterator permite acessar os elementos de um array sem saber como eles estão sendo representados, por isso torna-se irrelevante se a coleção de objetos é um ArrayList, HashTable ou qualquer outra coisa. Além disso, o padrão Iterator assume a responsabilidade de acessar os elementos sequencialmente e transfere essa tarefa para o objeto iterator.
Exemplo de implementação
Abaixo está um exemplo de implementação em Java usando o padrão Iterator.
Listagem 1: Exemplo de implementação do padrão Iterator
class MenuItem {
String nome;
MenuItem(String nome) {
this.nome = nome;
}
}
interface Iterator {
boolean hasNext();
Object next();
}
public class MenuIterator implements Iterator {
MenuItem[] itens;
int posicao = 0;
public MenuIterator(MenuItem[] itens) {
this.itens = itens;
}
public Object next() {
MenuItem menuItem = itens[posicao];
posicao++;
return menuItem;
}
public boolean hasNext() {
if (posicao >= itens.length || itens[posicao] == null) {
return false;
} else {
return true;
}
}
}
No exemplo acima, temos a classe principal MenuItem que é simplesmente um item de um menu que tem um nome, este poderia ser um menu que aparece na seção do menu de um site, por exemplo.
Abaixo está a interface "Iterator" que é implementado pela classe "MenuIterator", que será responsável pela iteração através da captura de menus que estarão em uma espécie de coleção de objetos como uma matriz ou um ArrayList. Uma situação interessante para pensar sobre a utilidade deste padrão é na situação em que, por exemplo, se tivéssemos uma classe que cria uma coleção de menus e, em seguida, seria preciso passar por este menu para exibir tudo nesta coleção, como seria você implementar sem usar a interface Iterator e MenuIterator? Abaixo está uma possível solução para resolver este problema:
Listagem 2: Exemplo de iteração sobre menus
public class ShowMenu {
public static void main(String args []) {
MenuItem [] menuItens = new MenuItem[4];
menuItens[0] = new MenuItem("Menu 1");
menuItens[1] = new MenuItem("Menu 2");
menuItens[2] = new MenuItem("Menu 3");
menuItens[3] = new MenuItem("Menu 4");
for (int i=0; i < menuItens.length; i++) {
System.out.println(menuItens[i].nome);
}
}
}
No exemplo acima, podemos ver que a iteração é visível no meio da classe, e se tivéssemos esta iteração em várias classes, então seria preciso mudá-lo, porque o tipo de coleção também mudou? E se fosse um menu de HashMap e não um array? Teríamos que mudar também a iteração. Pode-se imaginar se isso iria funcionar.
Então criamos anteriormente a Interface Iterator e a classe de implementação para Menus, MenuIterator. Abaixo está uma possível utilização do padrão Iterator para percorrer uma coleção de objetos de menu:
Listagem 3: Exemplo de iteração sobre menus usando o padrão Iterator
public class ShowMenu {
public static void main(String args []) {
MenuItem [] menuItens = new MenuItem[4];
menuItens[0] = new MenuItem("Menu 1");
menuItens[1] = new MenuItem("Menu 2");
menuItens[2] = new MenuItem("Menu 3");
menuItens[3] = new MenuItem("Menu 4");
Iterator menuIterator = new MenuIterator(menuItens);
while (menuIterator.hasNext()) {
MenuItem menuItem = (MenuItem)menuIterator.next();
System.out.println(menuItem.nome);
}
}
}
Podemos notar que toda a estrutura interna do padrão Iterator foi captada pelo Iterator produzindo um código muito mais limpo e claro. A única coisa que usamos são os métodos next () e hasNext ().
Vantagens do padrão Iterator
O padrão Iterator encapsula implementações de iterações, a partir de agora não precisamos mais para ver que tipo de coleção está sendo usado por objetos, como um ArrayList ou HashTable. Usando o padrão Iterator só precisa de um loop para lidar de forma polimórfica com qualquer coleção de itens, uma vez que apenas implementa o Iterator. Anteriormente, também foram ligados com o código de classes, como ArrayList, agora utiliza apenas uma interface (Iterator), lembre-se de sempre programar para interfaces.
Conclusão
O padrão Iterator permite o acesso sequencial aos elementos de um conjunto sem expor sua implementação subjacente. O padrão Iterator também é responsável por toda a tarefa de iteração, eliminando assim a responsabilidade adicional, simplificando, assim a sua aplicação e deixando a responsabilidade onde deveria estar. Finalmente, o padrão Iterator nos remete a dois princípios de bons projetos que são a alta coesão em classes projetadas em torno de um conjunto de funções relacionadas entre si e a responsabilidade única, onde a classe tem uma única finalidade ou responsabilidade. Apesar da coesão ter um conceito mais genérico, que está intimamente relacionado com o conceito de responsabilidade única.