Ebook grátis: Primeiros passos com Padrões de Projeto

Como desenvolvedores, nós enfrentamos vários problemas para lidar com a informação nos sistemas: estruturar e armazenar as informações, transformar dados para que possam ser lidos pelos usuário, agrupar dados de diferentes sistemas etc. Assim, desenhar um sistema e sua arquitetura nem sempre é uma tarefa simples e direta. Além disso, conforme aprendemos mais sobre o domínio e a maneira como o sistema é utilizado, percebemos que o desenho inicial não será suficiente e sentimos a necessidade de modificá-lo.

No entanto, a maioria dos desafios que encontramos não é único. Certamente o domínio do sistema pode ser diferente, mas a essência dos problemas continua a mesma: como lidar com informação. Assim surgiu a ideia dos Padrões de Projeto, como uma base coletiva de conhecimento com soluções para esses “problemas comuns” que encontramos todos os dias.

Ao longo do livro vamos ver como se deu o surgimento dos Padrões de Projeto e como eles foram adaptados para o desenvolvimento de software. Em seguida vamos analisar características do paradigma Orientado a Objetos e como eles buscam simplificar o desenvolvimento de aplicações. Por fim, serão explorados dois exemplos de padrões voltados para o desenvolvimento Orientado a Objetos, para exemplificar como eles podem ajudar na manutenção de sistemas.

O ebook pode ser baixado de graça em leanpub.com. Compartilhe com suas amigas e amigos!

Porque nomear um atributo como “type” não é uma boa ideia em Rails.

Ou: como utilizar o padrão Herança de Tabela Única (Single Table Inheritance) em Rails.

Se alguma vez você já tentou criar um atributo chamado “type” no seu modelo Active Record, provavelmente você teve algum problema, ou já sabia exatamente o que estava fazendo. Se você se encaixa no primeiro grupo, então esse post talvez possa lhe ajudar um pouco.

Um pequeno exemplo

Vamos criar um scaffold usando Rails da seguinte maneira:

rails generate scaffold Dealer name type company

Criamos o Dealer (Negociante) que no nosso dominio tem os atributos nome (nome da pessoa) e empresa (empregador). Adicionamos também um campo type, esse campo deve ter os valores “Seller” caso seja um vendedor ou “Buyer” caso seja um comprador.

Agora vamos no rails console para criar um novo Dealer da seguinte maneira:

Dealer.create name: "Zeca", company: "Uburu Inc", type: "Seller"

A seguinte exceção será lançada: ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: Seller is not a subclass of Dealer.

O começo da mensagem diz que o tipo da herança de tabela única é invalido pois a classe Seller não é uma subclasse de Dealer. Vamos ver um pouco mais sobre o padrão Single Table Inheritance (STI) [1]

Single Table Inheritance

O padrão STI está catalogado como parte dos Padrões de Arquitetura de Aplicações Empresariais (Patterns of Enterprise Application Architecture). O cenário de aplicação do padrão é quando você precisa representar uma herança em uma base de dados relacional.

A ideia para resolver o problema é bem simples, vamos adicionar um campo que vai dizer qual subclasse deve ser instanciada com aqueles dados. Voltando ao exemplo do post, podemos então ter uma superclasse chamada Dealer e duas subclasses Seller/Buyer que vão ter os mesmos atributos, mas lógicas diferentes.

O Rails já vem pronto para utilizarmos o STI. Basta utilizar que chamemos o campo que diferencia a classe de ‘type’. Por isso, no exemplo anterior, aquele exceção foi lançada. Criamos o modelo Dealer, mas ainda não temos o modelo Seller ou Buyer.

Para resolver o problema vamos criar o Seller e Buyer e fazer com que elas herdem de Dealer. Note que as classes Seller e Buyer não precisam herdar da classe ActiveRecord pois elas vão herdar de Dealer. O código seria algo do tipo:

class Seller < Dealer
end

Agora, se tentarmos criar o mesmo objeto de antes, veremos a seguinte saida:

Dealer.create name: "Zeca", company: "Uburu Inc", type: "Seller"
=> #<Seller id: 1, name: "Zeca", type: "Seller", company: "Uburu Inc", created_at: "2013-11-14 00:51:55", updated_at: "2013-11-14 00:51:55">

Ou seja, mesmo criando um Dealer, o Rails sabe que se trata de um Seller, graças ao STI. Se tentarmos criar um Seller também conseguimos o mesmo resultado:

Seller.create name: "Zeca", company: "Uburu Inc"
=> #<Seller id: 2, name: "Zeca", type: "Seller", company: "Uburu Inc", created_at: "2013-11-14 00:57:36", updated_at: "2013-11-14 00:57:36">

Veja também que o Rails já colocou o valor do atributo ‘type’ para “Seller” automaticamente. Assim você não precisa se preocupar em sempre criar um “Dealer” com o ‘type’ sendo “Seller” ou “Buyer”, basta utilizar a classes que você realmente precisa.

Se o nosso objetivo fosse só ter um campo chamado ‘type’ mas sem nenhuma herança podemos simplesmente renomear o campo para ‘alguma_coisa_type’ ou, se isso não for possível, podemos adicionar a seguinte linha na implementação da classe:

class Seller < ActiveRecord::Base
  self.inheritance_column = nil
end

Assim, o Rails não vai mais se importar com o ‘type’ para herança. Ou caso você precise configurar o atributo que define a herança como outro nome, também é possível passar o nome desejado para que o Rails trate o modelo como uma implementação do STI.

Referências

1. Martin Fowler, Single Table Inheritance. http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html

Mão na Massa: Model View Controller

Neste post especial da série de padrões de projeto, vamos analisar o tão conhecido padrão Model-View-Controller (MVC).

Este post será um pouco diferente dos outros da série, pois não vamos construir um exemplo real de utilização do padrão MVC, mas vamos analisá-lo sob o ponto de vista de vários frameworks em várias linguagens.

Problema:

Imagine que você trabalha em um projeto de uma loja online, nele é necessário que os clientes possam acessar o site da loja utilizando um desktop ou um dispositivo móvel. Também é necessário que a equipe que controla a parte operacional da loja acesse informações de estoque, para isso utilizando um aplicativo desktop para controle de estoque entre outras atividades. Além disso, também será necessário que o serviço da loja seja integrado com outros sistemas da empresa, precisando assim de comunicação através de XML ou JSON.

Ao analisar o problema, é fácil perceber o alto nível da questão. Não basta apenas analisar  um conjunto de classes e resolver o problema. Este é um exemplo de um problema arquitetural, pois as decisões vão implicar em alterações em vários outros locais do projeto. Por isso é que o padrão MVC é tido como um padrão arquitetural e não de design, como são os outros padrões da série Mão na Massa.

Voltando ao problema, analisando melhor é possível perceber que o banco de informações é praticamente o mesmo para as várias aplicações, no entanto a lógica do negócio e como os dados serão exibidos é bem diferente entre os aplicativos. O site do cliente precisa gerenciar carrinhos de compra, o site mobile precisa de algumas pequenas alterações no seu visual para se adaptar ao dispositivo e, da mesma forma, os outros aplicativos precisam sofrer alterações para o seu ambiente.

Quem acompanhou a série de posts sobre padrões de projeto deve ter visto o padrão de projeto Observer, se você não o conhece, dê uma olhada no post depois. Poderíamos utilizar o padrão Observer para centralizar o conjunto de dados e oferecer várias visões diferentes, de acordo com os aplicativos. No entanto o Observer não possui uma abrangência que possa ser estendida ao domínio do nosso problema, por isso vamos ver agora o padrão MVC.

Model View Controller (MVC)

A estrutura básica do MVC é também semelhante a do Observer:

O MVC busca organizar o projeto de uma maneira que facilite a reusabilidade e manutenção, e bons frameworks que utilizam o padrão MVC irão melhorar ainda mais esta capacidade.

Na camada de Model, ficam as classes que fazem a comunicação com o Banco de Dados, representando estas informações. A camada de Controller é responsável por gerenciar eventos e acionar as classes de modelo para realizar alterações nas informações. A camada View então vai exibir os novos dados do Model ao usuário.

Ao utilizar o padrão, alterações ficam contidas nas suas camadas, ou afetam o mínimo possível outras camadas. No exemplo do problema citado acima, as classes de modelo e controle seriam as mesmas para o site desktop e o site mobile, a única diferença seria na camada de visualização. Já o controle de estoque dos produtos aproveitaria a camada de modelo para extrair as informações necessárias ao seu setor.

O fluxo MVC vária de acordo com a implementação de cada framework, no entanto geralmente ele segue o esquema [1]:

  1. O usuário interage com a interface
  2. O controlador gerencia os eventos da interface, invocando uma ação apropriada
  3. O modelo é notificado da ação, alterando o estado do modelo
  4. A visão é notificada da alteração e é atualizada

Apesar do fluxo ser bem parecido com o do padrão Observer, as implementações do padrão MVC oferecem uma série de recursos a mais, tornando o MVC mais geral. Vamos ver então como alguns frameworks implementam o padrão MVC.

MVC e os Frameworks

O framework Rails, como falado neste post, utiliza Helpers, que são classes com métodos auxiliares, e Partials, que são “pequenas views” que podem ser renderizadas em vários lugares para permitir reaproveitamento de código.

Já o Spring Web MVC (framework Java) segue uma abordagem um pouco diferente [2]:

Pela imagem podemos ver que o Front Controller centraliza as chamadas e delega as responsabilidades de requisições, renderização, etc.

A implementação do Django (framework Python) possui nomenclaturas diferentes e um fluxo um pouco diferenciado [3]:

O Django utiliza o Model-Template-View, pois no framework uma View contém as regras de negócios, quais modelos serão utilizados, etc. A view do Django define apenas quais dados serão mostrados, e não como estes dados serão exibidos. Esta é a responsabilidade dos Templates, que apresentam os dados da View em vários formatos, como HTML, Javascript, XML, etc. Dai a diferença entre View e Templates.

Já o CodeIgniter possui uma implementação mais “pura” do padrão MVC [4]:

O esforço do framework é separar os códigos PHP e organizá-los de uma maneira que facilite a reusabilidade e a manutenção do sistema, evitando assim aquelas páginas que contém regras de negócio, acesso ao banco e código Javascript no mesmo arquivo.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Encontrou algum erro no código? Comente também. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] http://en.wikipedia.org/wiki/Model–view–controller

[2] http://static.springsource.org/spring/docs/2.0.x/reference/mvc.html

[3] http://www.profissionaisti.com.br/2009/04/entendendo-o-django/

[4] http://codeigniterbrasil.com/passos-iniciais/mvc-model-view-controller/

Classificação dos Padrões de Projeto GoF

Quem acompanha o blog viu que já falamos (e codificamos) todos os Padrões de Projeto catalogados no livro Padrões de Projeto do GoF. Apesar de parecer estranho falar de uma visão geral dos padrões depois dos padrões, fica mais fácil discutir sobre as relações entre padrões quando eles já são conhecidos.

Então vamos falar um pouco sobre as categorias dos padrões de projeto GoF. Segundo o livro, os padrões são divididos em três categorias: de Criação, Estrutural e Comportamental. Todos os padrões destas categorias tem um conjunto de características específicas, que motivam a categorização deles.

Antes de falar das categorias, é importante comentar que os padrões de objeto, além das categorias, podem ser classificados também em relação ao seu escopo: de Classe ou de Objetos. Padrões com escopo de Classe vão utilizar a herança para compor ou variar os objetos, mantendo a flexibilidade do sistema. Já os padrões de Objeto irão delegar as suas responsabilidades para um objeto.

A figura a seguir permite visualizar a classificação dos padrões de projeto, de acordo com os critérios falados:

Padrões de Criação

Os padrões de criação tem como intenção principal abstrair o processo de criação de objetos, ou seja, a sua instanciação. Desta maneira o sistema não precisa se preocupar com questões sobre, como o objeto é criado, como é composto, qual a sua representação real. Quando se diz que o sistema não precisa se preocupar com a instanciação do objeto quer dizer que, se ocorrer alguma mudança neste ponto, o sistema em geral não será afetado. Isso é a famosa flexibilidade que os padrões de projeto buscam.

Padrões de criação com escopo de classe vão utilizar herança para garantir que a flexibilidade. Por exemplo, o padrão Factory Method pode criar várias subclasses para criar o produto. Já os padrões com escopo de Objeto, como o Prototype, delegam para um objeto (no caso o protótipo) a responsabilidade de instanciar novos objetos.

Padrões Estruturais

Os padrões estruturais vão se preocupar em como as classes e objetos são compostos, ou seja, como é a sua estrutura. O objetivo destes padrões e facilitar o design do sistema identificando maneiras de realizar o relacionamento entre as entidades, deixando o desenvolvedor livre desta preocupação.

Os padrões com escopo de classe utilizam a herança para compor implementações ou interfaces. O padrão Adapter, por exemplo, pode definir uma nova interface para adaptar duas outras já existentes, assim uma nova classe é criada para adaptar uma interface a outra. Os padrões com escopo de objeto utilizam a composição de objetos para definir uma estrutura. Por exemplo, o padrão Composite define (explicitamente) uma estrutura de hierárquica para classes primitivas e compostas em um objeto.

Padrões Comportamentais

Os padrões comportamentais atuam sobre como responsabilidades são atribuídas as entidades, ou seja, qual o comportamento das entidades. Estes padrões facilitam a comunicação entre os objetos, distribuindo as responsabilidades e definindo a comunicação interna.

Padrões com escopo de classe utilizam herança para realizar a distribuição do comportamento. Um bom exemplo é o padrão Template Method, que fornece um algoritmo (comportamento) padrão e deixa as subclasses definirem alguns pontos da execução do algoritmo. Já os padrões de objetos vão compor os objetos para definir a comunicação, como o padrão Mediator, que define um objeto que realiza a comunicação muitos-para-muitos.

Para saber mais sobre os padrões, acesse a página Padrões de Projeto, lá estão listado todos os padrões comentados aqui, com exemplos de utilização e código em Java. Além disso, outro site que detalha mais os padrões e traz exemplos em diversas linguagens é o SourceMaking. Como livros, o principal é o livro de referência, Padrões de Projeto, e o livro da séria Use a Cabeça!: Padrões de Projeto.

Mão na massa: Prototype

Para encerrar a série de posts sobre os padrões de projeto, o padrão Prototype!

Problema:

O padrão Prototype é mais um dos padrões de criação. Assim seu intuito principal é criar objetos. Este intuito é muito parecido com todos os outros padrões criacionais, tornando todos eles bem semelhantes.

Para explicitar ainda mais esta semelhança vamos analisar o mesmo problema apresentado na discussão sobre o padrão Factory Method.

O problema consiste em uma lista de carros que o cliente precisa utilizar, mas que só serão conhecidos em tempo de execução. Vamos analisar então como o problema pode ser selecionado utilizando o padrão Prototype.

Prototype

A intenção do padrão:

“Especificar tipos de objetos a serem criados usando uma instância protótipo e criar novos objetos pela cópia desse protótipo.” [1]

Pela intenção podemos perceber como o padrão vai resolver o problema. Precisamos criar novos objetos a partir de uma instância protótipo, que vai realizar uma cópia de si mesmo e retornar para o novo objeto.

A estrutura do padrão inicia então com definição dos objetos protótipos. Para garantir a flexibilidade do sistema, vamos criar a classe base de todos os protótipos:

public abstract class CarroPrototype {
	protected double valorCompra;

	public abstract String exibirInfo();

	public abstract CarroPrototype clonar();

	public double getValorCompra() {
		return valorCompra;
	}

	public void setValorCompra(double valorCompra) {
		this.valorCompra = valorCompra;
	}
}

Definimos a partir dela que todos os carros terão um valor de compra, que será manipulado por um conjunto de getters e setters. Também garantimos que todos eles possuem os métodos para exibir informações e para realizar a cópia do objeto.

Para exemplificar uma classe protótipo concreta, vejamos a seguinte classe:

public class FiestaPrototype extends CarroPrototype {

	protected FiestaPrototype(FiestaPrototype fiestaPrototype) {
		this.valorCompra = fiestaPrototype.getValorCompra();
	}

	public FiestaPrototype() {
		valorCompra = 0.0;
	}

	@Override
	public String exibirInfo() {
		return "Modelo: Fiesta\nMontadora: Ford\n" + "Valor: R$"
				+ getValorCompra();
	}

	@Override
	public CarroPrototype clonar() {
		return new FiestaPrototype(this);
	}

}

Note que no início são definidos dois construtores, um protegido e outro público. O construtor protegido recebe como parâmetro um objeto da própria classe protótipo. Este é o chamado construtor por cópia, que recebe um outro objeto da mesma classe e cria um novo objeto com os mesmos valores nos atributos. A necessidade deste contrutor será vista no método de cópia.

O método exibirInfo() exibe as informações referentes ao carro, retornando uma string com as informações. Ao final o método de clonagem retorna um novo objeto da classe protótipo concreta. Para garantir que será retornado um novo objeto, vamos utilizar o construtor por cópia definido anteriormente.

Agora, sempre que for preciso criar um novo objeto FiestaPrototype vamos utilizar um único protótipo. Pense nesta operação como sendo um método fábrica, para evitar que o cliente fique responsável pela criação dos objetos e apenas utilize os objetos.

Para verificar esta propriedade, vamos analisar o código cliente a seguir, que faz uso do padrão prototype.

	public static void main(String[] args) {
		PalioPrototype prototipoPalio = new PalioPrototype();

		CarroPrototype palioNovo = prototipoPalio.clonar();
		palioNovo.setValorCompra(27900.0);
		CarroPrototype palioUsado = prototipoPalio.clonar();
		palioUsado.setValorCompra(21000.0);

		System.out.println(palioNovo.exibirInfo());
		System.out.println(palioUsado.exibirInfo());
	}

Observe com atenção o cliente. Criamos dois protótipos de carros, cada um deles é criado utilizando o prótipo PalioPrototype instanciado anteriormente. Ou seja, a partir de uma instância de um protótipo é possível criar vários objetos a partir da cópia deste protótipo.

Outro detalhe é que, se a operação de clonagem não fosse feita utilizando o construtor de cópia, quando a chamada ao setValorCompra fosse feita, ela mudaria as duas instâncias, pois elas referenciariam ao mesmo objeto.

O diagrama UML que representa a estrutura do padrão Prototype utilizada nesta solução é o seguinte:

Um pouco de teoria

Inicialmente é fácil ver que o padrão Prototype oferece as mesmas vantagens que outros padrões de criação: esconde os produtos do cliente, reduz o acoplamento e oferece maior flexibilidade para alterações nas classes produtos.

A diferença básica deste padrão é a flexibilidade. Por exemplo: o cliente instancia vários protótipos, quando um deles não é mais necessário, basta removê-lo. Se é preciso adicionar novos protótipos, basta incluir a instanciação no cliente. Essa flexibilidade pode ocorrer inclusive em tempo de execução.

O padrão Prototype também poderia fazer uso do Abstract Factory. Imagine que uma classe instância todos os protótipos e oferece métodos para copiar estes protótipos, ela seria uma fábrica de famílias de produtos.

Os produtos do Prototype podem ser alterados livremente apenas mudando os atributos, como no exemplo onde criamos um palio novo e um palio usado. No entanto é preciso garantir que o método de cópia esteja implementado corretamente, para evitar que a alteração nos valores mude todas as instâncias.

O padrão Prototype leva grande vantagem quando o processo de criação de seus produtos é muito caro, ou mais caro do que uma clonagem. No lugar de criar um Proxy para cada produto, basta definir, no objeto protótipo, como será essa inicialização, ou parte dela.

Um detalhe que torna o Prototype único em relação aos outros padrões de criação é que ele utiliza objetos para criar os produtos, enquanto os outros utilizam classes. Dependendo da arquitetura ou linguagem/plataforma do problema, é possível tirar vantagem deste comportamento.

Código fonte completo

O código completo pode ser baixado no seguinte repositório Git: https://github.com/MarcosX/Padr-es-de-Projeto.

Os arquivos estão como um projeto do eclipse, então basta colocar no seu Workspace e fazer o import.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Encontrou algum erro no código? Comente também. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.

Mão na massa: Visitor

Problema:

É comum em projetos, onde você precisa criar sua própria estrutura de dados, que sejam necessários implementar várias operações sobre este conjunto de dados. Geralmente, esta responsabilidade é delegada para a própria classe que representa a estrutura.

Para discutirmos melhor, suponha que em um projeto você precisa lidar com uma estrutura de dados muito complexa, uma árvore binária por exemplo (tá, não é tão complexo mas serve pro exemplo). É comum que sejam implementados métodos para percorrer a árvore, como por exemplo: in-order, pre-order e post-order. [2]

O problema em implementar estes métodos diretamente na classe que representa a árvore é que é preciso ter um alto nível de certeza de que estas operações não mudarão, ou que serão utilizadas apenas nesta estrutura, pois o custo para realizar alterações seria muito grande, uma vez que o sistema dependerá da definição desta classe.

Outro problema é que, várias outras estruturas de dados vão utilizar uma abordagem semelhante. Por exemplo, caso seja implementado uma árvore AVL, que utiliza os mesmo dados e as mesmas operações de percurso, não seria possível reutilizar este método (reutilizar NÃO É ctrl+c ctrl+v!).

Vejamos então um pouco sobre o padrão Visitor, que vai nos ajudar bastante a resolver este problema.

Visitor

Como de costume, vamos a intenção do padrão:

“Representar uma operação a ser executada nos elementos de uma estrutura de objetos. Visitor permite definir uma nova operação sem mudar as classes dos elementos sobre os quais opera.” [1]

Pela intenção já é possível ver como o padrão vai nos ajudar. A sua ideia é separar as operações que serão executadas em determinada estrutura de sua representação. Assim, incluir ou remover operações não terá nenhum efeito sobre a interface da estrutura, permitindo que o resto do sistema funcione sem depender de operações específicas.

Vamos então começar definindo a estrutura de dados a ser utilizada. A seguinte classe representa o nó da árvore:

public class No {
	protected int chave;
	No esquerdo, direito;

	public No(int chave) {
		this.chave = chave;
		esquerdo = null;
		direito = null;
	}

	public String toString() {
		return String.valueOf(chave);
	}

	public int getChave() {
		return chave;
	}

	public No getEsquerdo() {
		return esquerdo;
	}

	public void setEsquerdo(No esquerdo) {
		this.esquerdo = esquerdo;
	}

	public No getDireito() {
		return direito;
	}

	public void setDireito(No direito) {
		this.direito = direito;
	}

}

Definimos então o nó básico da árvore, que contém a chave e os nós esquerdo e direito. Além disso o método “toString()” permite que a estrutura seja exibida na tela com o sysout. Agora vamos ver a estrutura árvore, que vai manter todos estes elementos.

public class ArvoreBinaria {
	No raiz;
	int quantidadeDeElementos;

	public ArvoreBinaria(int chaveRaiz) {
		raiz = new No(chaveRaiz);
		quantidadeDeElementos = 0;
	}

	public void inserir(int chave) {
		...
	}

	public void remover(int chave){
		...
	}

	public void buscar(int chave){
		...
	}

	public void aceitarVisitante(ArvoreVisitor visitor) {
		visitor.visitar(raiz);
	}
}

A estrutura árvore vai então possuir o nó raiz da árvore e algumas outras informações sobre a árvore. Omiti a implementação dos métodos da árvore para simplificar o exemplo. O detalhe importante desta classe é o método “aceitarVisitante()”, ele recebe um objeto ArvoreVisitor e passa a sua raiz para ele. Ai começa a implementação do padrão de verdade.

A estrutura de dados vai possuir um método que recebe um objeto visitante. Deste método ela vai chamar o método visitar, do objeto visitante, e vai passar os seus dados para o objeto visitante. Dai em diante, o objeto visitante vai poder realizar as operações necessárias. Vamos então começar com a implementação de ArvoreVisitor:

public interface ArvoreVisitor {

	void visitar(No no);

}

Esta classe vai apenas definir a interface de visita de um nó. Todas as operações vão receber um objeto No e a partir dai vão implementar suas operações. Por exemplo, vejamos a implementação de um método de percurso in-order.

public class ExibirInOrderVisitor implements ArvoreVisitor {

	@Override
	public void visitar(No no) {
		if (no == null)
			return;
		this.visitar(no.getEsquerdo());
		System.out.println(no);
		this.visitar(no.getDireito());
	}

}

Seguindo a ideia de implementação de percurso in-order [2], primeiro é feita a visita ao nó esquerdo, em seguida mostramos no terminal o nó e ao fim é feita a visita ao nó direito. Com esta simples implementação temos o método de percurso in-order da árvore, sem precisar alterar a sua estrutura.

Implementar várias outras operações fica muito simples ao utilizar este padrão. As implementações dos outros métodos de percurso ficam triviais. Uma implementação mais complexo, também fica mais simplificada. Por exemplo, caso seja necessário implementar um método que exibe os nós de uma maneira indentada, de acordo com seu nível na árvore.

public class ExibirIndentadoVisitor implements ArvoreVisitor {

	@Override
	public void visitar(No no) {
		if (no == null) {
			return;
		}
		System.out.println(no);
		visitar(no.getEsquerdo(), 1);
		visitar(no.getDireito(), 1);
	}

	private void visitar(No no, int qtdEspacos) {
		if (no == null) {
			return;
		}
		for (int i = 0; i < qtdEspacos; i++) {
			System.out.print("-");
		}
		System.out.println(no);
		visitar(no.getEsquerdo(), qtdEspacos + 1);
		visitar(no.getDireito(), qtdEspacos + 1);
	}

}

Este método conta a quantidade de espaços para indentação a partir de cada nível de recursão do método. A única restrição das classes visitantes é que implementem o método para visitar. Quaisquer outras operações que precisem de suporte podem ser implementadas.

Um exemplo de cliente seria:

	public static void main(String[] args) {
		ArvoreBinaria arvore = new ArvoreBinaria(7);

		arvore.inserir(15);
		arvore.inserir(10);
		arvore.inserir(5);
		arvore.inserir(2);
		arvore.inserir(1);
		arvore.inserir(20);

		System.out.println("### Exibindo em ordem ###");
		arvore.aceitarVisitante(new ExibirInOrderVisitor());
		System.out.println("### Exibindo pre ordem ###");
		arvore.aceitarVisitante(new ExibirPreOrdemVisitor());
		System.out.println("### Exibindo pós ordem ###");
		arvore.aceitarVisitante(new ExibirPostOrderVisitor());
		System.out.println("### Exibindo identado ###");
		arvore.aceitarVisitante(new ExibirIndentadoVisitor());
	}

O diagrama UML que representa esta solução é o seguinte:

Um pouco de teoria

Como foi exemplificado, o padrão Visitor oferece uma excelente alternativa quando é necessário realizar uma série de operações sobre um conjunto de dados, dado que estas operações são pouco estáveis, ou seja, sofrem alterações constantemente. Um outro exemplo que evidencia mais a aplicabilidade do padrão é na construção de compiladores, como mostrado em [1], onde existem vários tipos de nós da árvore de sintaxe abstrata, e o conjunto de operações é indefinido.

Assim, para decidir pela utilização do padrão Visitor é necessário ter certeza de que a estrutura dos elementos seja bem estável (não sofra alterações ao longo do projeto) e que a interface desta estrutura permita acesso suficiente para os objetos visitantes. Elementos podem ter interfaces diferentes, contato que estas interface sejam estáveis e provejam acesso às classes visitantes.

Outro detalhes importante é que a estrutura de dados depende da interface das classes visitantes. Já a classe visitante precisa ter conhecimento sobre os vários tipo de elementos, pois cada um deles poderá ser visitado de uma maneira diferente. Para exemplificar esta afirmação, suponha o seguinte cenário:

São utilizadas várias estruturas de dados, como vetores, listas, árvores binárias, árvores B, heaps, etc. Todas estas classes oferecem uma interface simples: adicionar, remover e buscar elementos. Já um método visitante atuaria de maneiras diferentes em cada uma delas. O método de percurso de uma árvore binária é diferente do método de percurso de uma lista, ou de uma árvore B. Assim, na classe visitante, é preciso ter um método para visitar cada um destes tipos de estruturas, embora todas elas tenham uma interface em comum.

Se por um lado o padrão facilita a adição de novas operações sobre o conjunto de estruturas, fica muito difícil, uma vez que o projeto está utilizando o padrão, incluir novos tipos de elementos, pois cada novo elemento vai gerar alterações em todas as classes visitantes.

Padrões que utilizem estruturas de dados para representação podem utilizar o padrão Visitor, por exemplo, uma estrutura Composite ou Interpreter, pode oferecer um método de visita e atribuir para as classes visitantes a responsabilidade das operações sobre seu conjunto de dados.

Já que o padrão Visitor é utilizado para percorrer um conjunto de dados, qual a diferença dele e do padrão Iterator? A intenção do padrão Iterator é fornecer uma maneira de percorrer os elementos de uma estrutura, expondo seus elementos e deixando para o cliente a responsabilidade de operar sobre estes. O padrão Visitor oferece operações sobre elementos, sem expor seu conteúdo, ou delegar responsabilidades extras para o cliente.

Código fonte completo

O código completo pode ser baixado no seguinte repositório Git: https://github.com/MarcosX/Padr-es-de-Projeto.

Os arquivos estão como um projeto do eclipse, então basta colocar no seu Workspace e fazer o import.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Encontrou algum erro no código? Comente também. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.
[2] WIKIPEDIA. Tree Traversal: http://en.wikipedia.org/wiki/Tree_traversal. Acesso em 24 de novembro de 2011.

Mão na massa: State

Problema:

A troca de estados de um objeto é um problema bastante comum. Tome como exemplo o personagem de um jogo, como o Mario. Durante o jogo acontecem várias trocas de estado com o Mario, por exemplo, ao pegar uma flor de fogo o mario pode crescer, se estiver pequeno, e ficar com a habilidade de soltar bolas de fogo.

Desenvolvendo um pouco mais o pensamento temos um conjunto grande de possíveis estados, e cada transição depende de qual é o estado atual do personagem. Como falado anteriormente, ao pegar uma flor de fogo podem acontecer quatro ações diferentes, dependendo de qual o estado atual do mario:

Se Mario pequeno -> Mario grande e Mario fogo
Se Mario grande -> Mario fogo
Se Mario fogo -> Mario ganha 1000 pontos
Se Mario capa -> Mario fogo

Todas estas condições devem ser checadas para realizar esta única troca de estado. Agora imagine o vários estados e a complexidade para realizar a troca destes estados: Mario pequeno, Mario grande, Mario flor e Mario pena.

Pegar Cogumelo:
Se Mario pequeno -> Mario grande
Se Mario grande -> 1000 pontos
Se Mario fogo -> 1000 pontos
Se Mario capa -> 1000 pontos

Pegar Flor:
Se Mario pequeno -> Mario grande e Mario fogo
Se Mario grande -> Mario fogo
Se Mario fogo -> 1000 pontos
Se Mario capa -> Mario fogo

Pegar Pena:
Se Mario pequeno -> Mario grande e Mario capa
Se Mario grande -> Mario capa
Se Mario fogo -> Mario fogo
Se Mario capa -> 1000 pontos

Levar Dano:
Se Mario pequeno -> Mario morto
Se Mario grande -> Mario pequeno
Se Mario fogo -> Mario grande
Se Mario capa -> Mario grande

Com certeza não vale a pena investir tempo e código numa solução que utilize várias verificações para cada troca de estado. Para não correr o risco de esquecer de tratar algum estado e deixar o código bem mais fácil de manter, vamos analisar como o padrão State pode ajudar.

State

A intenção do padrão:

“Permite a um objeto alterar seu comportamento quando seu estado interno muda. O objeto parecerá ter mudado de classe.” [1]

Pela intenção podemos ver que o padrão vai alterar o comportamento de um objeto quando houver alguma mudança no seu estado interno, como se ele tivesse mudado de classe.

Para implementar o padrão será necessário criar uma classe que contém a interface básica de todos os estados. Como definimos anteriormente o que pode causar alteração nos estados do objeto Mario, estas serão as operações básicas que vão fazer parte da interface.

public interface MarioState {
	MarioState pegarCogumelo();

	MarioState pegarFlor();

	MarioState pegarPena();

	MarioState levarDano();
}

Agora todos os estados do mario deverão implementar as operações de troca de estado. Note que cada operação retorna um objeto do tipo MarioState, pois como cada operação representa uma troca de estados, será retornado qual o novo estado o mario deve assumir.

Vejamos então uma classe para exemplificar um estado:

public class MarioPequeno implements MarioState {

	@Override
	public MarioState pegarCogumelo() {
		System.out.println("Mario grande");
		return new MarioGrande();
	}

	@Override
	public MarioState pegarFlor() {
		System.out.println("Mario grande com fogo");
		return new MarioFogo();
	}

	@Override
	public MarioState pegarPena() {
		System.out.println("Mario grande com capa");
		return new MarioCapa();
	}

	@Override
	public MarioState levarDano() {
		System.out.println("Mario morto");
		return new MarioMorto();
	}

}

Percebemos que a classe que define o estado é bem simples, apenas precisa definir qual estado deve ser trocado quando uma operação de troca for chamada. Vejamos agora outro exemplo de classe de estado:

public class MarioCapa implements MarioState {

	@Override
	public MarioState pegarCogumelo() {
		System.out.println("Mario ganhou 1000 pontos");
		return this;
	}

	@Override
	public MarioState pegarFlor() {
		System.out.println("Mario com fogo");
		return new MarioFogo();
	}

	@Override
	public MarioState pegarPena() {
		System.out.println("Mario ganhou 1000 pontos");
		return this;
	}

	@Override
	public MarioState levarDano() {
		System.out.println("Mario grande");
		return new MarioGrande();
	}

}

Bem simples não? Novos estados são adicionados de maneira bem simples. Vejamos então como seria o objeto que vai utilizar o estados, o Mario:

public class Mario {
	protected MarioState estado;
	
	public Mario() {
		estado = new MarioPequeno();
	}

	public void pegarCogumelo() {
		estado = estado.pegarCogumelo();
	}

	public void pegarFlor() {
		estado = estado.pegarFlor();
	}

	public void pegarPena() {
		estado = estado.pegarPena();
	}

	public void levarDano() {
		estado = estado.levarDano();
	}
}

A classe mario possui uma referência para um objeto estado, este estado vai ser atualizado de acordo com as operações de troca de estados, definidas logo em seguida. Quando uma operação for invocada, o objeto estado vai executar a operação e se atualizará automaticamente. Como exemplo de utilização, vejamos o seguinte código cliente:

public static void main(String[] args) {
	Mario mario = new Mario();
	mario.pegarCogumelo();
	mario.pegarPena();
	mario.levarDano();
	mario.pegarFlor();
	mario.pegarFlor();
	mario.levarDano();
	mario.levarDano();
	mario.pegarPena();
	mario.levarDano();
	mario.levarDano();
	mario.levarDano();
}

Por este código é possível avaliar todas as transições e todos os estados.

O diagrama UML a seguir resume visualmente as relações entre as classes:

Um pouco de teoria

Pelo visto no exemplo, o padrão é utilizado quando se precisa isolar o comportamento de um objeto, que depende de seu estado interno. O padrão elimina a necessidade de condicionais complexos e que frequentemente serão repetidos. Com o padrão cada “ramo” do condicional acaba se tornando um objeto, assim você pode tratar cada estado como se fosse um objeto de verdade, distribuindo a complexidade dos condicionais.

Incluir novos estados também é muito simples, basta criar uma nova classe e atualizar as operações de transição de estados. Com a primeira solução seriam necessários vários milhões de ifs novos e a alteração dos já existentes, além do grande risco de esquecer algum estado. Outra grande vantagem é que fica claro, com a estrutura do padrão, quais são os estados e quais são as possíveis transições.

O padrão State não define aonde as transições ocorrem, elas podem ser colocadas dentro das classes de estado ou dentro da classe que armazena o estado. No exemplo vimos que dentro de cada estado são definidos os novos objetos que são retornados. A principal vantagem desta solução é que fica mais simples adicionar os estados, cada novo estado define suas transições. O problema é que assim cada classe de estado precisa ter conhecimento sobre as outras subclasses, e se alguma delas mudar, é provável que a mudança se espalhe.

É comum que objetos estados obedeçam a outros dois padrões: Singleton e Flyweight. Estados singleton são capazes de manter informações, mesmo com as constantes trocas de estados. Estados Flyweight permitem o compartilhamento entre objetos que vão utilizar a mesma máquina de estado.

O padrão State tem semelhanças com outros dois padrões: Strategy e Bridge. Falando primeiro sobre o Strategy, note que a ideia é muito parecida: eliminar vários ifs complexos espalhados utilizando subclasses. A diferença básica é que o State é mais dinâmico que o Strategy, pois ocorrem várias trocas de objetos estados, os próprios objetos estados realizam as transições.

A semelhança com o padrão Bridge também pode ser notada facilmente pelo diagrama UML, no entanto a diferença está na intenção dos padrões. A intenção do Bridge é permitir que tanto a implementação quanto a interface possam mudar independentemente. No State a ideia é realmente mudar o comportamento de um objeto.

Código fonte completo

O código completo pode ser baixado no seguinte repositório Git: https://github.com/MarcosX/Padr-es-de-Projeto.

Os arquivos estão como um projeto do eclipse, então basta colocar no seu Workspace e fazer o import.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Encontrou algum erro no código? Comente também. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.