Mão na massa: Abstract Factory

Falamos no post anterior sobre o famoso Factory Method, agora vamos demonstrar o Abstract Factory!

Problema

Vamos utilizar como o problema o mesmo discutido no post sobre o Factory Method. Queremos representar um sistema que, dado um conjunto de carros deve manipulá-los. A diferença é que, desta vez, precisamos agrupar os carros em conjuntos. A ideia de conjuntos é agrupar objetos que tem comportamentos parecidos. Para exemplificar veja como os objetos devem ser organizados:

Sedan:

  • Siena – Fiat
  • Fiesta Sedan – Ford

Popular:

  • Palio – Fiat
  • Fiesta – Ford

Ou seja, agora precisamos agrupar um conjunto de carros Sedan e outro conjunto de carros Popular para cada uma das fábricas.

Se considerarmos os carros Sedan como um produto, poderíamos criar uma classe produto para cada novo carro e, consequentemente uma classe fábrica para cada novo produto.

O problema é que, desta forma não teríamos a ideia de agrupamento dos produtos. Por exemplo, para criar os carros da Fiat precisaríamos de uma fábrica de carros populares da Fiat e outra fábrica de carros sedan da Fiat. Essa separação dificultaria tratar os carros de uma mesma marca.

Então surge o padrão Abstract Factory, que leva a mesma ideia do Factory Method para um nível mais alto.

Abstract Factory

Vejamos a intenção do Padrão Abstract Factory:

“Fornecer uma interface para criação de famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.”[1]

Então, de acordo com a descrição da intenção do padrão, nós poderemos criar famílias de objetos, no nosso exemplo seriam a família de carros Sedan e a família de carros Populares. Sem mais demora vamos ao código.

Inicialmente vamos escrever a classe interface para criação de Fábricas. Cada fabrica vai criar um objeto de cada tipo, ou seja, para o nosso exemplo, precisaremos de dois métodos fábrica, um para carros Sedan e outro para carros Populares:

public interface FabricaDeCarro {
	CarroSedan criarCarroSedan();
	CarroPopular criarCarroPopular();
}

E agora vamos escrever as classes que vão criar os carros de fato:

public class FabricaFiat implements FabricaDeCarro {

	@Override
	public CarroSedan criarCarroSedan() {
		return new Siena();
	}

	@Override
	public CarroPopular criarCarroPopular() {
		return new Palio();
	}

}

Pronto! Todas as outras fábricas precisam apenas implementar esta pequena interface. Vamos então para o lado do produto. Como já comentamos os produtos são divididos em dois grupos, Sedan e Popular, em que cada um deles possui um conjunto de atributos e métodos próprios. Para o nosso exemplo vamos considerar que existe um método para exibir informações de um carro Sedan e outro para exibir informações de carros Populares. As interfaces seriam assim:

public interface CarroPopular {
	void exibirInfoPopular();
}
public interface CarroSedan {
	void exibirInfoSedan();
}

Apesar de os métodos basicamente executarem a mesma operação, diferindo apenas no nome, suponha que os carros Populares estão em um banco de dados e os carros Sedan em outros, assim cada método precisaria criar sua própria conexão.

Agora vamos ver os produtos concretos:

public class Palio implements CarroPopular {

	@Override
	public void exibirInfoPopular() {
		System.out.println("Modelo: Palio\nFábrica: Fiat\nCategoria:Popular");
	}

}
public class Siena implements CarroSedan {

	@Override
	public void exibirInfoSedan() {
		System.out.println("Modelo: Siena\nFábrica: Fiat\nCategoria:Sedan");
	}

}

Pronto, definido as fábricas e os produtos vamos analisar o código cliente:

	public static void main(String[] args) {
		FabricaDeCarro fabrica = new FabricaFiat();
		CarroSedan sedan = fabrica.criarCarroSedan();
		CarroPopular popular = fabrica.criarCarroPopular();
		sedan.exibirInfoSedan();
		System.out.println();
		popular.exibirInfoPopular();
		System.out.println();

		fabrica = new FabricaFord();
		sedan = fabrica.criarCarroSedan();
		popular = fabrica.criarCarroPopular();
		sedan.exibirInfoSedan();
		System.out.println();
		popular.exibirInfoPopular();
	}

Perceba que criamos uma referência para uma fábrica abstrata e jogamos nela qualquer fábrica, de acordo com o que necessitamos. De maneira semelhante criamos referências para um carro Popular e para um carro Sedan, e de acordo com nossas necessidades fomos utilizando os carros dos fabricantes.

Essa é a estrutura do projeto de fábricas de carros Sedan e Popular:

Um pouco de teoria

Como você pode notar, este padrão sofre do mesmo problema do Factory Method, ou seja criamos uma estrutura muito grande de classes e interfaces para resolver o problema de criação de objetos. No entanto, segundo o princípio da Segregação de Interface [2] isto é uma coisa boa, pois o nosso código cliente fica dependendo de interfaces simples e pequenas ao invés de depender de uma interface grande e que nem todos os métodos seria utilizados.

Para exemplificar suponha que criássemos apenas uma interface para carros, nela precisaríamos definir os métodos de exibir informações tanto para Populares quanto para Sedan. O porblema é que, dado um carro qualquer não dá pra saber se ele é um sedan ou um popular. E o que fazer para implementar o método de Populares em carros Sedan (e vice-versa)?

Ou seja, criar várias interfaces simples e pequenas é melhor do que uma interface faz-tudo. Então qual o problema com o padrão?

Pense como seria se tivéssemos que inserir uma nova família de produtos, por exemplo Sedan de Luxo. Do lado dos produtos apenas definiríamos a interface e implementaríamos os produtos. Mas do lado das fábricas será necessário inserir um novo método de criação em TODAS as fábricas. Tudo bem que o método de criação no exemplo é bem simples, então seria apenas tedioso escrever os códigos para todas as classes.

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. SOLID. Disponível em: http://en.wikipedia.org/wiki/SOLID_(object-oriented_design). Acesso em: 15 set. 2011.