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 do padrão Factory Method é quando queremos criar um mesmo tipo de produto, mas com diversas fábricas, cada fábrica funcionando de uma maneira própria e possui uma lógica específica que afeta o produto final.
Participantes de uma Factory Method e suas relações
Aplicar o Factory Method ajuda da seguinte maneira: para cada forma de fabricar, é criada uma classe fábrica que sabe apenas sobre suas próprias configurações, e todas as classes fábricas criam os mesmos objetos de produto.
Ao aplicar o Factory Method, cada um dos ifs externos que determina a forma de fabricar seria extraído para uma classe fábrica, e cada um dos ifs internos seria extraído para um método.
O diagrama de utilização do Factory Method é simples, a única diferença em relação ao Simple Factory é que vamos ver mais de uma classe fábrica criando os produtos.
Definir uma interface para criar um objeto, mas deixar as subclasses decidirem que classe instanciar.
O Factory Method permite adiar a instanciação para subclasses.
Virtual Constructor
uma classe não pode antecipar a classe de objetos que deve criar;
uma classe quer que suas subclasses especifiquem os objetos que criam;
classes delegam responsabilidade para uma dentre várias subclasses auxiliares, e você quer localizar o conhecimento de qual subclasse auxiliar que é a delegada.
O Factory Method separa o código de construção do produto do código que realmente usa o produto. Portanto, é mais fácil estender o código de construção do produto independentemente do restante do código. Assim, é possível desacoplar a superclasse da criação de uma dependência.
Com a criação das instâncias na subclasse, apenas elas ficam acopladas as classes concretas da hierarquia.
Dessa forma, caso uma nova instância da dependência precise ser utilizada pela superclasse, basta criar uma nova subclasse que retorne aquela instância.
Metamodelo original Factory Method
Modelo de aplicação Factory Method
Product: define a interface de objetos que o método fábrica cria.
ConcreteProduct: implementa a interface de Product.
Creator: Declara o método fábrica, o qual retorna um objeto do tipo Product.
Creator pode também definir uma implementação por omissão do método factory que retorna por omissão um objeto ConcreteProduct.
Pode chamar o método factory para criar um objeto Product.
ConcreteCreator: Redefine o método-fábrica para retornar a uma instância de um ConcreteProduct.
Creator depende das suas subclasses para definir o método fábrica de maneira que retorne uma instância do ConcreteProduct apropriado.
class Main { // Aqui a classe Main representa a classe Cliente
public static void main(String[] args) {
System.out.println("Hello world!");
Creator factory = new ConcreteCreatorX(); // define a uma forma de fabricar
// Pede para a fábrica criar uma instância de produto
Product p01 = factory.createProduct("A");
// Usando o produto criado normalmente...
System.out.println(p01.getName());
p01.operation();
// Alterando a forma de fabricar...
factory = new ConcreteCreatorY();
p01 = factory.createProduct("A");
p01.operation();
}
}
public abstract class Creator {
public abstract Product createProduct(String type); // método fábrica
}
public class ConcreteCreatorX extends Creator {
public Product createProduct(String type) {
Product product = null;
if (type.equalsIgnoreCase("A")) {
product = new ConcreteProductA();
} else if (type.equalsIgnoreCase("B")) {
product = new ConcreteProductB();
} else {
throw new IllegalArgumentException("Tente novamente");
}
System.out.println(product.getName() + " criado com sucesso via método fábrica X.");
return product;
}
}
public class ConcreteCreatorY extends Creator {
public Product createProduct(String type) {
Product product = null;
if (type.equalsIgnoreCase("A")) {
product = new ConcreteProductA();
} else if (type.equalsIgnoreCase("B")) {
product = new ConcreteProductB();
} else {
throw new IllegalArgumentException("Tente novamente");
}
System.out.println(product.getName() + " criado com sucesso via método fábrica Y.");
return product;
}
}
public abstract class Product {
String name;
public String getName() {
return name;
}
public void operation() {
System.out.println("Executando método da instância do " + name);
}
}
public class ConcreteProductA extends Product {
public ConcreteProductA() {
name = "Produto A";
}
}
public class ConcreteProductB extends Product {
public ConcreteProductB() {
name = "Produto B";
}
}
O padrão Factory Method sugere que você substitua as chamadas diretas de construção de objeto (new) por chamadas para um método de fábrica específico.
Os objetos ainda são criados por new, mas que estão sendo chamados de dentro do método de fábrica.
Métodos de fábrica retornam objetos do tipo produto (Product).
DIFERENTES FORMAS DE IMPLEMENTAR:
As duas principais variações do padrão Factory Method são:
(1) o caso em que a classe Creator é uma classe abstrata e não fornece uma implementação para o método-fábrica que ela declara (mostrada no código acima); e
(2) o caso quando o Creator é uma classe concreta e fornece uma implementação por omissão para o método-fábrica.
Também é possível ter uma classe abstrata que define uma implementação por omissão, mas isto é menos comum.
Fornece ganchos para subclasses. Criar objetos dentro de uma classe com um método fábrica é sempre mais flexível do que criar um objeto diretamente. Factory Method dá às subclasses um gancho para fornecer uma versão estendida de um objeto, o conceito dessa implementação é conhecido como método hook. A partir de métodos hook é possível que as subclasses insiram comportamentos em métodos que estão implementados na superclasse.
Conecta hierarquias de classe paralelas. Nem sempre o método-fábrica precisa ser chamado por um Creator. Clientes podem achar os métodos-fábrica úteis, especialmente no caso de hierarquias de classe paralelas.
Hierarquias de classe paralelas ocorrem quando uma classe delega alguma(s) das suas responsabilidades para uma classe com de hierarquia separada.
Uma desvantagem em potencial dos métodos-fábrica é que os clientes podem ter que fornecer subclasses da classe Creator somente para criar um objeto ConcreteProduct em particular. Usar subclasses é bom quando o cliente tem que fornecer subclasses a Creator de qualquer maneira.
Abstract Factory: Uma fábrica abstrata é frequentemente implementada utilizado o padrão Factory Method.
Template Method: Factory Methods são usualmente chamados dentro de Template Methods.
Prototype: Uma implementação de Prototype não exige a subclassificação de Creator. Contudo, frequentemente necessita de uma operação Initialize na classe Product. A Creator usa Initialize para iniciar o objeto. O Factory Method não exige uma operação desse tipo.
É uma boa prática o uso de convenções de nomenclatura que tornam claro que você está usando métodos-fábrica.
class Main {
public static void main(String[] args) {
Pizzaria f01 = new PizzariaConvencional();
Pizzaria f02 = new PizzariaFornoLenha();
Pizza p01 = f01.criarPizza(true);
p01.assar();
p01 = f02.criarPizza(true);
p01.assar();
}
}
abstract class Pizzaria {
protected abstract Pizza criarPizza(boolean bordaRecheada);
}
// Implementação concreta da fábrica de pizzas
public class PizzariaConvencional extends Pizzaria {
@Override
protected Pizza criarPizza(boolean bordaRecheada) {
Pizza pizza = null;
if (bordaRecheada == true) {
pizza = new PizzaComBorda();
} else {
pizza = new PizzaSemBorda();
}
System.out.println(" Pizza criada com sucesso para assar em forno convencional.");
return pizza;
}
}
// Implementação concreta da fábrica de pizzas
public class PizzariaFornoLenha extends Pizzaria {
@Override
protected Pizza criarPizza(boolean bordaRecheada) {
Pizza pizza = null;
if (bordaRecheada == true) {
pizza = new PizzaComBorda();
} else {
pizza = new PizzaSemBorda();
}
System.out.println(" Pizza criada com sucesso para assar em forno a lenha.");
return pizza;
}
}
interface Pizza {
void preparar();
void assar();
void embalar();
}
class PizzaComBorda implements Pizza {
@Override
public void preparar() {
System.out.println("Preparando Pizza com Borda Recheada.");
}
@Override
public void assar() {
System.out.println("Assando Pizza com Borda Recheada.");
}
@Override
public void embalar() {
System.out.println("Embalando Pizza com Borda Recheada.");
}
}
class PizzaSemBorda implements Pizza {
@Override
public void preparar() {
System.out.println("Preparando Pizza SEM Borda Recheada.");
}
@Override
public void assar() {
System.out.println("Assando Pizza SEM Borda Recheada.");
}
@Override
public void embalar() {
System.out.println("Embalando Pizza SEM Borda Recheada.");
}
}
A API Java possui vários exemplos de classes que utilizam o padrão de projeto Factory Method:
Calendar (java.util.Calendar): A classe Calendar fornece um método estático getInstance() que retorna uma instância de Calendar. Esse método é um exemplo do padrão Factory Method, pois encapsula a lógica de criação de instâncias de Calendar, permitindo que os clientes obtenham uma instância adequada sem conhecer os detalhes de implementação.
NumberFormat (java.text.NumberFormat): A classe NumberFormat possui métodos estáticos como getNumberInstance() e getCurrencyInstance() que retornam instâncias de NumberFormat adequadas para formatar números e moedas. Esses métodos também seguem o padrão Factory Method, pois encapsulam a criação de instâncias com base nas necessidades do cliente.
DateFormat (java.text.DateFormat): A classe DateFormat possui métodos estáticos como getDateInstance() e getTimeInstance() que retornam instâncias de DateFormat apropriadas para formatar datas e horas. Esses métodos são exemplos de Factory Methods, permitindo que os clientes obtenham instâncias de DateFormat adequadas sem precisar conhecer os detalhes de criação.
java.util.EnumSet e java.util.EnumMap: As classes EnumSet e EnumMap possuem métodos estáticos of() que retornam instâncias de EnumSet e EnumMap respectivamente. Esses métodos seguem o padrão Factory Method, pois fornecem uma maneira conveniente de criar conjuntos e mapas baseados em enumerações.
Esses são apenas alguns exemplos da API Java que utilizam o padrão Factory Method. Essas classes fornecem métodos de fábrica para criar instâncias de forma flexível e encapsulada, permitindo que os clientes obtenham objetos adequados sem precisar se preocupar com a lógica de criação detalhada.