Factory Method


Resumo

Ao invés do cliente instanciar objetos através da chamada a new, ficando acoplado com a classe concreta que ele instancia, ele chama um método de criação (Factory Method).

Por exemplo, no código: Carro carro = new Carro() o objeto carro é uma instância da classe concreta Carro. Isso está pré-determinado neste código acoplando a instância à classe concreta.

E se o objetivo é instanciar carro com uma classe concreta determinada em tempo de execução, uma solução é: carro = criaCarro().

Note que o método criaCarro() pode criar um carro tendo como retorno um Carro Abstrato. Logo, criaCarro() concreto determina qual carro concreto criar.

Neste exemplo criaCarro() é um Factory Method.

Usando o Factory Method uma classe cria um objeto pelo TIPO Abstrato e uma subclasse concreta sua determina qual a classe concreta do objeto a criar.

Mudar a subclasse concreta que cria o objeto permite mudar a classe do objeto criado.

Participantes

Produto - Define a interface (o TIPO) dos objetos criados pelo Factory Method

ProdutoConcreto - Implementa a interface Produto

Criador - Declara o Factory Method que retorna um objeto do tipo Produto

Às vezes, o Criador não é abstrato. Pode envolver uma classe concreta que tenha uma implementação padrão para o Factory Method para retornar um objeto com algum tipo ProdutoConcreto.

CriadorConcreto - Faz override do Factory Method para retornar uma instância de ProdutoConcreto

 

Código do Exemplo Acoplado

/**
 * @author - Edeyson Andrade Gomes - www.edeyson.com.br
 *
 * A própria Classe é responsável por determinar a 
 * estrutura da Casa e os Objetos Concretos (de Aço) a
 * usar em sua construção. Note que Estrutura e Objeto Concreto
 * da construção estão Acoplados.
 */


public class CasaAço {
    private Quarto  quarto1;
    private Quarto  quarto2;
    private Sala  sala1;
    private Sala  sala2;

    public CasaAço() {
        montaEstruturaCasa();
    }
    
    protected void montaEstruturaCasa() {        
        Porta  porta = new PortaAço();
        Parede  parede = new ParedeAço(porta);
        
        sala1 = new SalaAço();
        sala2 = new SalaAço();
        quarto1 = new QuartoAço();
        quarto2 = new QuartoAço();
        
        sala1.setParedeSul(parede);
        sala2.setParedeNorte(parede);
        
        sala1.setParedeNorte(new ParedeAço());
        sala1.setParedeOeste(new ParedeAço());
        
        porta = new PortaAço();
        parede = new ParedeAço(porta);
        sala2.setParedeOeste(parede);
        sala2.setParedeSul(new ParedeAço());

        porta = new PortaAço();
        parede = new ParedeAço(porta);
        sala1.setParedeLeste(parede);
        quarto1.setParedeOeste(parede);        

        porta = new PortaAço();
        parede = new ParedeAço(porta);
        sala2.setParedeLeste(parede);
        quarto2.setParedeOeste(parede);        
        
        quarto2.setParedeLeste(new ParedeAço());    
        quarto2.setParedeSul(new ParedeAço());        
        
        parede = new ParedeAço(porta);
        quarto2.setParedeNorte(parede);
        quarto1.setParedeSul(parede);                
    }
}

A Classe CasaAço determina TODO objeto concreto a usar através de NEW. Ou seja, todos os objetos concretos são de Aço.

O problema do acoplamento pode ser encontrado quando se desejar criar uma Casa com a mesma estrutura, porém de Madeira. E se ela puder ser criada em Vidro? Que classes serão necessárias?

Como é o método montaEstruturaCasa que determina a estrutura da casa, teríemos que criar uma nova classe Casa repetindo código para mudar o que é instanciado, pois NEW fixa o objeto concreto criado. Por exemplo, para a Casa de Madeira temos que mudar todos os NEW para criar objetos de madeira:

public class CasaMadeira {
    /* Atributos da Casa */
    ...     
    public CasaMadeira() {
        montaEstruturaCasa();
    }
    
    protected void montaEstruturaCasa() {
        Porta  porta = new PortaMadeira();
        Parede  parede = new ParedeMadeira(porta);
        
        sala1 = new SalaMadeira();
        sala2 = new SalaMadeira();
        quarto1 = new QuartoMadeira();
        quarto2 = new QuartoMadeira();
        
        sala1.setParedeSul(parede);
        sala2.setParedeNorte(parede);
        ... Repetição de TODO o código alterando apenas os NEW.
       }

}

Vantagem do Factory Method

CasaComFactoryMethod define métodos abstratos para criar Produtos, são eles:

    protected abstract Porta  criaPorta();
    protected abstract Parede  criaParede();
    protected abstract Parede  criaParede(Porta  p);
    protected  abstract Quarto  criaQuarto();
    protected  abstract Sala  criaSala();

Com isso o método montaEstruturaCasa() não precisa chamar NEW para criar a estrutura da casa, focando apenas nesta e não nos objetos concretos que serão usados.

Basta que suas subclasses implementem tais métodos que elas determinarão os ProdutosConcretos a usar na estrutura. Por exemplo:

public class CasaComFactoryMethodAço 
        extends CasaComFactoryMethod {
            
    protected Porta  criaPorta() {
         return new PortaAço();
    }
    
    protected Parede  criaParede() {
         return new ParedeAço();
    }

    protected Parede  criaParede(Porta  p) {
         return new ParedeAço(p);
    }

    protected Quarto  criaQuarto() {
         return new QuartoAço();
    }
    
    protected Sala  criaSala() {
         return new SalaAço();
    }
    
}

 

Quando Usar

Quando uma classe (o criador) não pode antecipar a classe dos objetos que deve criar e/ou quer que suas subclasses especifiquem os objetos criados

Consequências

Factory Method elimina a necessidade de colocar classes específicas da aplicação no código.

O código só lida com o TIPO Produto.

O código pode funcionar com qualquer classe ProdutoConcreto

Factory Method provê ganchos para subclasses

Criar objetos dentro de uma classe com um Factory Method é sempre mais flexível do que criar objetos diretamente.

O Factory Method provê um gancho para que subclasses forneçam uma versão estendida de um objeto.

 

Exemplo Acoplado

Deseja-se criar uma Casa que possua Salas, Quartos, Paredes e Portas, que podem ser construídas em Aço, Vidro ou Madeira.

Casa de Aço possui Salas, Quartos, Paredes e Portas de Aço. Vidro e Madeira seguem o mesmo raciocínio.

O projeto inicial da Casa deve ter duas Salas e dois Quartos, com paredes com e sem porta, como apresenta a Figura a seguir:

Os Produtos da solução seriam Sala, Quarto, Porta e Parede, que no caso são abstratos. Os Produtos Concretos são Sala, Quarto, Porta e Parede de Aço, Madeira ou Vidro. Vejamos na Figura Modelo Acoplado:

salaAcoplado

 

Os modelos para Quarto, Porta e Parede são análogos, com a mesma hierarquia.

Problema

O problema é que para cada novo tipo de casa será necessário repetir o código todo (pois é ele que determina a estrutura da casa) mudando apenas o que se cria com NEW.

Os modelos para Quarto, Porta e Parede são análogos, com a mesma hierarquia.

Solução com Factory Method

Uma solução que use o Factory Method deve retardar o acoplamento com o Produto Concreto. Assim, ao montarmos a estrutura da classe vamos retardar a chamada a NEW.

Os Produtos serão instanciados através da chamada a métodos de criação na forma criaProduto().

Por exemplo:

public abstract class CasaComFactoryMethod {
    private Quarto  quarto1, quarto2;
    private Sala  sala1, sala2;
    
    /* Factory Methods     */
    protected abstract Porta  criaPorta();
    protected abstract Parede  criaParede();
    protected abstract Parede  criaParede(Porta  p);
    protected  abstract Quarto  criaQuarto();
    protected  abstract Sala  criaSala();
        
    public CasaComFactoryMethod() {
        System.out.println(\"Criando \" + this.getClass().getName());        
        montaEstruturaCasa();
    }
    
    protected void montaEstruturaCasa() {    
        Porta  porta = criaPorta();
        Parede  parede = criaParede(porta);
        
        sala1 = criaSala();
        sala2 = criaSala();
        quarto1 = criaQuarto();
        quarto2 = criaQuarto();
        
        sala1.setParedeSul(parede);
        sala2.setParedeNorte(parede);
        
        sala1.setParedeNorte(criaParede());
        sala1.setParedeOeste(criaParede());
        
        porta = criaPorta();
        parede = criaParede(porta);
        sala2.setParedeOeste(parede);
        sala2.setParedeSul(criaParede());

        porta = criaPorta();
        parede = criaParede(porta);
        sala1.setParedeLeste(parede);
        quarto1.setParedeOeste(parede);        

        porta = criaPorta();
        parede = criaParede(porta);
        sala2.setParedeLeste(parede);
        quarto2.setParedeOeste(parede);        
        
        quarto2.setParedeLeste(criaParede());    
        quarto2.setParedeSul(criaParede());        
        
        parede = criaParede(porta);
        quarto2.setParedeNorte(parede);
        quarto1.setParedeSul(parede);                
    }
}

Soluções em Java

Construtora de Casas