Introdução

Os Padrões de Projeto também conhecidos como Design Patterns (em inglês) são soluções já encontradas, testadas e comprovadas e que podemos aplicar aos projetos sem ter que reinventar a roda. Diversos Padrões de Projeto já foram catalogados e são um conjunto de boas práticas que devemos seguir e utilizar em projetos de software orientados a objetos.

Padrões de Projetos basicamente descrevem soluções para problemas recorrentes no desenvolvimento de sistemas de software orientados a objetos. Um padrão de projeto estabelece um nome e define o problema, a solução, quando aplicar esta solução (contexto) e suas consequências.

Além disso, os Padrões de Projeto também definem um vocabulário comum que facilita a comunicação, documentação e aprendizado dos sistemas de software.

Os padrões de projetos são classificados como:

  • Criacionais – definem a criação de objetos;
  • Estruturais – definem a composição de classes e objetos;
  • Comportamentais – definem a interação entre classes e objetos.

Neste artigo veremos mais sobre o Padrão de projeto Iterator que é utilizado em diversos projetos e na API do Java.

Funcionamento

O Padrão de Projeto Iterator tem como objetivo encapsular uma iteração. O Padrão de Projeto Iterator depende de uma interface chamada Iterator, conforme pode ser vista abaixo:

Exemplo da Interface Iterator

Figura 1: Exemplo da Interface Iterator

O método hasNext() determina se existem mais elementos para serem iterados. O método next() retorna o próximo objeto da iteração.

Depois que já possuímos a interface podemos implementar iteradores para qualquer tipo de coleção de objetos sejam matrizes, listas, hashtables, etc. Para cada coleção de objetos que queira-se encapsular a iteração cria-se uma implementação para a interface Iterator definida acima. Por exemplo para encapsular uma iteração para um menu teríamos a classe abaixo:

Exemplo de Implementação da Interface Iterator

Figura 2: Exemplo de Implementação da Interface Iterator

Um método que poderia fazer parte do nosso diagrama é o “remove” para remover objetos da coleção, no entanto, este é um método opcional, não precisamos necessariamente fornecer recursos de remoção.

O Padrão Iterator é definido como: “O Padrão Iterator fornece uma maneira de acessar sequencialmente os elementos de um objeto agregado sem expor a sua representação subjacente”. Portanto, temos que o padrão Iterator permite acessarmos um a um os elementos de um agregado mesmo sem saber como eles estão sendo representados, assim torna-se irrelevante se a coleção de objetos está num ArrayList, HashTable ou que quer que seja. Além disso, o Padrão Iterator assume a responsabilidade de acessar sequencialmente os elementos e transfere essa tarefa para o objeto Iterador, dessa forma o objeto agregador tem a sua interface e implementação simplificadas, não sendo mais o responsável pela iteração.

Exemplo de Implementação

Segue abaixo um exemplo de implementação em Java utilizando 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 possui um nome, este poderia ser um menu que apareceria na seção de menu de um site, por exemplo.

Abaixo temos a interface “Iterator” que é implementada pela classe “MenuIterator” que será responsável por iterar pela coleção de menus que estará em algum tipo de coleção de objetos como uma matriz ou um ArrayList. Uma situação interessante de se 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 depois precisaríamos percorrer esse menu para mostrar tudo que há nesta coleção, como você implementaria sem utilizar a interface Iterator e a classe MenuIterator acima? Segue abaixo uma possível solução de como resolveríamos este problema:

Listagem 2: Exemplo de iteração sobre os menus


public class MostraMenu {
	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 notar que a iteração está toda visível no meio da classe, e se tivéssemos essa iteração em diversas classes e agora precisaríamos mudá-la, pois o tipo da coleção também sofreu alterações? E se agora fosse um Hashmap de menus e não mais uma matriz? Teríamos que mudar também a iteração. Pode-se imaginar o trabalho que isso daria.

Por isso anteriormente criamos a interface Iterator e a classe de implementação MenuIterator para os Menus. Abaixo segue 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 os menus usando o padrão Iterator


public class MostraMenu {
	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 iterador foi abstraída pelo Padrão de Projeto Iterator ficando de uma forma muito mais limpa e clara. A única coisa que utilizamos são os métodos next() e hasNext().

Vantagens do Padrão Iterator

O Padrão Iterator encapsula as implementações das iterações, a partir de agora não precisamos mais ver que tipo de coleção está sendo utilizada pelos objetos como um ArrayList ou um HashTable. Com a utilização do Padrão Iterator precisamos apenas de um loop para lidarmos polimorficamente com qualquer coleção de itens desde que ela apenas implemente o Iterator. Anteriormente também estávamos com o código vinculado a classes como ArrayList, agora usamos apenas uma interface (Iterator), lembre-se de programar sempre para interfaces.

Conclusão

O Padrão Iterator permite o acesso sequencial aos elementos de um agregado sem expor a sua implementação subjacente. O Padrão Iterator também é responsável por toda a tarefa de iteração, retirando assim do agregado a responsabilidade, dessa forma simplificando a interface e a sua implementação deixando a responsabilidade onde deveria estar. Por fim, o padrão Iterator nos remete a dois princípios de bons projetos que é da alta coesão com as classes projetadas em torno de um conjunto de funções relacionadas entre si e da Responsabilidade Única, onde a classe possui uma única finalidade ou responsabilidade. Apesar da coesão ter um conceito mais genérico, ela está estritamente relacionada com o conceito da Responsabilidade Única.

Bibliografia

  • Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra. Head First Design Patterns. O'Reilly Media, 2004.
  • Gamma, E., Helm, R., Johnson, R., Vlissides, J. Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley, 2010.