Extrair a lógica de criação de objetos para classes e métodos específicos é o objetivo dos padrões Factory.
O problema que padrões fábrica resolvem é criar instâncias de objetos separadas do resto da lógica; por isso são classificados como padrões de criação. Contudo, Simple Factory, Factory Method e Abstract Factory diferem bastante na solução para o problema, o que torna a escolha de qual usar muito dependente do contexto em que serão usados.
O contexto de utilização da Abstract Factory é um pouco mais complexo do que o Factory Method. Seguindo a analogia das fábricas, existirão múltiplas fábricas que criam múltiplos tipos de produtos.
Nos padrões anteriores, foram abordados problemas referentes à complexidade de criação dos objetos, além de seu desacoplamento da classe cliente e da própria classe que está sendo criada.
Um outro problema surge quando mais de um objeto relacionado precisa ser criado. Muitas vezes existem famílias de objetos nos quais existem classes relacionadas em hierarquias paralelas.
Um exemplo é quando uma mesma API é implementada por diversos fornecedores. Nessa caso, cada um deles fornece implementações diferentes para as mesmas abstrações. Não faz sentido utilizar uma classe de um fornecedor junto com outra classe de outro, pois apesar de obedecerem às mesmas abstrações elas não podem ser misturadas.
Esse padrão é destinado à criação de uma família de objetos relacionados. Dessa forma, se todos os objetos relacionados forem obtidos a partir da mesma fábrica, não haverá inconsistência entre eles.
Participantes de uma Abstract Factory e suas relações
Fornecer uma interface para criação de famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.
Kit (toolkit)
um sistema deve ser independente de como seus produtos são criados, compostos ou representados;
um sistema deve ser configurado como um produto de uma família de múltiplos produtos;
uma família de objetos-produto for projetada para ser usada em conjunto, e você necessita garantir esta restrição;
você quer fornecer uma biblioteca de classes de produtos e quer revelar somente suas interfaces, não suas implementações.
Metamodelo original Abstract Factory
Modelo de aplicação Abstract Factory
AbstractFactory: declara uma interface para operações que criam objetos-produto abstratos.
Concrete Factory: implementa as operações que criam objetos-produto concretos.
AbstractProduct: declara uma interface para um tipo de objeto-produto
ConcreteProduct: define um objeto-produto a ser criado pela correspondente fábrica concreta e implementa a interface de Abstract Product.
Client
Normalmente, uma única instância de uma classe ConcreteFactory é criada em tempo de execução. Essa fábrica concreta cria objetos-produto que têm uma implementação particular.
Para criar diferentes objetos-produto, os clientes deveriam usar uma fábrica concreta diferente;
AbstractFactory adia a criação dos objetos-produto para as suas subclasses ConcreteFactory.
class Main { // Aqui a classe Main representa a classe Cliente
public static void main(String[] args) {
ConcreteFactoryX factoryX = new ConcreteFactoryX(); // cria fábrica de produtos na versão X
ConcreteFactoryY factoryY = new ConcreteFactoryY(); // cria fábrica de produtos na versão Y
// Pede para a fábrica X criar uma instância do produto A
AbstractProductA productA = factoryX.createProductA();
// Pede para a fábrica Y criar uma instância do produto B
AbstractProductB productB = factoryY.createProductB();
// Usando o produto criado normalmente...
productA.operation();
productB.operation();
}
}
public abstract class AbstractFactory {
public abstract AbstractProductA createProductA(); // método fábrica A
public abstract AbstractProductB createProductB(); // método fábrica B
}
public class ConcreteFactoryX extends AbstractFactory {
public AbstractProductA createProductA() {
AbstractProductA product = new ConcreteProductAX();
System.out.println("Produto A criado com sucesso via método X");
return product;
}
public AbstractProductB createProductB() {
AbstractProductB product = new ConcreteProductBX();
System.out.println("Produto B criado com sucesso via método X");
return product;
}
}
public class ConcreteFactoryY extends AbstractFactory {
public AbstractProductA createProductA() {
AbstractProductA product = new ConcreteProductAY();
System.out.println("Produto A criado com sucesso via método Y");
return product;
}
public AbstractProductB createProductB() {
AbstractProductB product = new ConcreteProductBY();
System.out.println("Produto B criado com sucesso via método Y");
return product;
}
}
public abstract class AbstractProductA {
String name;
public String getName() {
return name;
}
public void operation() {
System.out.println("Executando método do Produto A " + name);
}
}
public class ConcreteProductAX extends AbstractProductA {
public ConcreteProductAX() {
name = "versão X";
}
}
public class ConcreteProductAY extends AbstractProductA {
public ConcreteProductAY() {
name = "versão Y";
}
}
public abstract class AbstractProductB {
String name;
public String getName() {
return name;
}
public void operation() {
System.out.println("Executando método do Produto B " + name);
}
}
public class ConcreteProductBX extends AbstractProductB {
public ConcreteProductBX() {
name = "versão X";
}
}
public class ConcreteProductBY extends AbstractProductB {
public ConcreteProductBY() {
name = "versão Y";
}
}
As classes ProdutoA e ProdutoB representam as classes pertencentes a uma família. Dessa forma, a família “X” é representada pelas implementa- ções ProdutoAX e ProdutoBX e o similar ocorre com a família “Y”. O exemplo apresenta apenas dois tipos de produto e duas famílias, porém em uma implementação desse padrão pode haver mais de cada um deles.
A classe FabricaAbstrata é a que abstrai a criação de uma família de objetos. Cada implementação é responsável pela criação de uma das famílias de objeto e possui métodos para a criação de cada um dos tipos de objeto da família. Como pode ser visto no diagrama, cada família possui uma implementação da Abstract Factory. Por exemplo, a FabricaFamiliaX cria instâncias das classes ProdutoAX e ProdutoBX.
DICAS:
1. Fábricas como singletons. Uma aplicação necessita somente de uma instância de uma Concrete Factory por família de produto, sendo melhor implementada como um Singleton.
2. Criando os produtos. AbstractFactory somente declara uma interface para criação de produtos. Fica a cargo das subclasses de ConcreteProducts criá-los efetivamente. A maneira mais comum de fazer isso é definir um método-fábrica (ver FactoryMethod ) para cada produto. Uma fábrica concreta especificará seus produtos redefinindo (override) o método-fábrica para cada um. Embora esta implementação seja simples, exige uma nova subclasse de uma fábrica concreta para cada família de produtos, ainda que as famílias de produto tenham somente diferenças pequenas.
3. Definindo fábricas extensíveis. AbstractFactory normalmente define uma operação diferente para cada tipo de produto que pode produzir. Os tipos de produtos estão codificados nas assinaturas das operações. O acréscimo de um novo tipo de produto exige a mudança da interface de AbstractFactory e de todas as classes que dependem dela.
Isolar as classes concretas. O padrão Abstract Factory ajuda a controlar as classes de objetos criadas por uma aplicação. Uma vez que a fábrica encapsula a responsabilidade e o processo de criar objetos-produto, isola os clientes das classes de implementação. Clientes manipulam as instâncias através das suas interfaces abstratas. Nomes de classes-produto ficam isolados na implementação da fábrica concreta; eles não aparecem no código cliente.
Facilitar a troca de famílias de produtos. A classe de uma fábrica concreta aparece apenas uma vez numa aplicação – isto é, quando é instanciada. Isso torna fácil mudar a fábrica concreta que uma aplicação usa. Ela pode usar diferentes configurações de produtos simplesmente trocando a fábrica concreta. Uma vez que a fábrica abstrata cria uma família completa de produtos, toda família de produtos muda de uma só vez.
Promover a harmonia entre produtos. Quando objetos-produto numa família são projetados para trabalharem juntos, é importante que uma aplicação use objetos de somente uma família de cada vez. AbstractFactory torna fácil assegurar isso.
É difícil de suportar novos tipos de produtos. Estender fábricas abstratas para produzir novos tipos de Produtos não é fácil. Isso se deve ao fato de que a interface de AbstractFactory fixa o conjunto de produtos que podem ser criados. Suportar novos tipos de produto exige estender a interface da fábrica, o que envolve mudar a classe AbstractFactory e todas as suas subclasses. Discutimos uma solução para este problema na seção de Implementação.
Em resumo, é fácil adicionar uma nova família de objetos,
porém é difícil adicionar um novo tipo de objeto na família!
As classes AbstractFactory são frequentemente implementadas com Factory Method;
Também podem ser implementadas usando Prototype;
Uma fábrica concreta é frequentemente um Singleton.
class Main {
public static void main(String[] args) {
FabricaCarros f01 = new FabricaCarrosLuxo();
FabricaCarros f02 = new FabricaCarrosEconomico();
f01.montarCarro();
f02.montarCarro();
}
}
public abstract class FabricaCarros {
Motor motor;
Pneu pneu;
public void montarCarro() {
this.motor = this.criarMotor();
this.motor.fabricar();
this.pneu = this.criarPneu();
this.pneu.fabricar();
System.out.println("Carro montado!");
}
public abstract Motor criarMotor();
public abstract Pneu criarPneu();
}
class FabricaCarrosEconomico extends FabricaCarros {
@Override
public Motor criarMotor() {
return new MotorEconomico();
}
@Override
public Pneu criarPneu() {
return new PneuEconomico();
}
}
class FabricaCarrosLuxo extends FabricaCarros {
@Override
public Motor criarMotor() {
return new MotorLuxo();
}
@Override
public Pneu criarPneu() {
return new PneuLuxo();
}
}
interface Motor {
void fabricar();
}
class MotorEconomico implements Motor {
@Override
public void fabricar() {
System.out.println("Fabricando motor econômico...");
}
}
class MotorLuxo implements Motor {
@Override
public void fabricar() {
System.out.println("Fabricando motor de luxo...");
}
}
interface Pneu {
void fabricar();
}
class PneuEconomico implements Pneu {
@Override
public void fabricar() {
System.out.println("Fabricando pneu econômico...");
}
}
class PneuLuxo implements Pneu {
@Override
public void fabricar() {
System.out.println("Fabricando pneu de luxo...");
}
}
A API Java possui alguns exemplos de classes que utilizam o padrão de projeto Abstract Factory:
javax.xml.parsers.DocumentBuilderFactory: A classe DocumentBuilderFactory é uma fábrica abstrata que fornece métodos estáticos para criar instâncias de javax.xml.parsers.DocumentBuilder, que é responsável por criar objetos org.w3c.dom.Document. A DocumentBuilderFactory permite que você escolha entre diferentes implementações de DocumentBuilder, dependendo dos requisitos do seu aplicativo.
javax.crypto.Cipher: A classe Cipher é uma fábrica abstrata que fornece métodos estáticos para criar instâncias de javax.crypto.Cipher, que é usado para realizar operações criptográficas, como criptografia e descriptografia. A Cipher permite que você escolha entre diferentes algoritmos de criptografia e modos de operação, dependendo das necessidades do seu aplicativo.
java.sql.Connection: A interface Connection é uma fábrica abstrata que fornece métodos para criar instâncias de java.sql.Statement e java.sql.PreparedStatement, que são usados para executar consultas SQL em bancos de dados. A Connection permite que você escolha entre diferentes implementações de Statement e PreparedStatement, dependendo do banco de dados que está sendo usado.
java.util.Calendar: Embora não seja uma fábrica abstrata no sentido estrito do padrão Abstract Factory, a classe Calendar fornece um método estático getInstance(), que retorna uma instância de Calendar. Essa instância é criada com base na implementação do calendário do sistema, que é determinada pela configuração local do ambiente em que o programa está sendo executado.
Esses são apenas alguns exemplos da API Java que utilizam o padrão Abstract Factory. Essas classes fornecem métodos de fábrica abstratos ou estáticos para criar objetos de diferentes tipos, permitindo que você escolha entre diferentes implementações ou comportamentos, dependendo das necessidades do seu aplicativo.