Padrões de Projeto
Factory Method
Fábio Gondim
Período: 2010.1
“Um projeto que não leva em
consideração a possibilidade
de mudanças está sujeito ao
risco de uma grande
reformulação no futuro.”
[GoF]
“Especificar um nome de uma classe
quando cria um objeto faz com que
você se comprometa com uma
particular implementação, em vez de
se comprometer com uma determinada
interface. Este compromisso pode
complicar futuras mudanças.
Para
evitá-lo, crie objetos indiretamente
.”
[GoF]
Factory Method
Veremos um exemplo, passo-a-passo,
adaptado do livro Head First - Design
Patterns, de Eric Freeman e Elisabeth
Freeman, para compreender a motivação
e utilização do padrão. O exemplo mostra
a evolução de uma modelagem para um
sistema hipotético de uma pizzaria.
public classPizzaria{ public Pizza pedirPizza() {
Pizzapizza = new Pizza(); pizza.preparar(); pizza.assar(); pizza.fatiar(); pizza.embalar(); returnpizza; } }
No início era apenas uma pequena pizzaria...
public Pizza pedirPizza(String tipo) {
Pizza pizza= null; if (tipo.equals(“Mussarela”)) {
pizza = newPizzaMussarela(); } else if (tipo.equals(“Calabresa”)) {
pizza = newPizzaCalabresa(); } else if (tipo.equals(“Grega”)) {
pizza = newPizzaGrega(); } pizza.preparar(); pizza.assar(); pizza.fatiar(); pizza.embalar(); returnpizza; }
... que certamente não prosperaria com apenas um tipo de pizza! Pizza passa a ser abstrata e surgem subtipos.
public Pizza pedirPizza(String tipo) { Pizza pizza= null;
if (tipo.equals("Mussarela")) { pizza = newPizzaMussarela(); } else if (tipo.equals("Calabresa")) { pizza = newPizzaCalabresa(); } else if (tipo.equals(“Grega")) {
pizza = new PizzaGrega(); } pizza.preparar(); pizza.assar(); pizza.fatiar(); pizza.embalar(); returnpizza; }
Certamente surgiram mudanças! Tipos foram eliminados...
A pizza grega não estava tendo saída.
public Pizza pedirPizza(String tipo) { Pizza pizza = null;
if (tipo.equals("Mussarela")) { pizza = newPizzaMussarela(); } else if (tipo.equals("Calabresa")) { pizza = new PizzaCalabresa(); } else if (tipo.equals(“Portuguesa")) {
pizza = newPizzaPortuguesa(); } else if (tipo.equals(“Napolitana")) {
pizza = newPizzaNapolitana(); } else if (tipo.equals(“Marguerita")) { pizza = newPizzaMarguerita(); }
//... }
... e novos tipos foram criados.
A concorrência lançava novos produtos e a pizzaria não queria ser ultrapassada!
public Pizza pedirPizza(String tipo) { Pizza pizza = null;
if (tipo.equals("Mussarela")) {
pizza = new PizzaMussarela(); } else if (tipo.equals("Calabresa")) { pizza = new PizzaCalabresa(); } else if (tipo.equals(“Grega")) {
pizza = new PizzaGrega(); } pizza.preparar(); pizza.assar(); pizza.fatiar(); pizza.embalar(); return pizza; }
Muitas mudanças estão ocorrendo aqui. Vamos separar o que varia e encapsular a criação de objetos.
public classSimplePizzaFactory{ public Pizza criarPizza(String tipo){
Pizza pizza= null;
if (tipo.equals("Mussarela")) { pizza = newPizzaMussarela(); } else if (tipo.equals("Calabresa")) { pizza = newPizzaCalabresa(); } else if (tipo.equals("Portuguesa")) {
pizza = newPizzaPortuguesa(); } else if (tipo.equals("Napolitana")) { pizza = newPizzaNapolitana(); } else if (tipo.equals("Marguerita")) { pizza = newPizzaMarguerita(); }
returnpizza; }
}
A classe SimplePizzaFactory será responsável pelo que varia: criação dos objetos.
public class Pizzaria {
// Nossa pizzaria agora contém uma fabrica SimplePizzaFactory fabrica;
public Pizzaria(SimplePizzaFactory fabrica) { this.fabrica = fabrica;
}
public static void main(String[] args) {
Pizzaria pizzaria = new Pizzaria(new SimplePizzaFactory()); Pizza pizza = pizzaria.pedirPizza("Mussarela");
}
public Pizza pedirPizza(String tipo) { Pizza pizza = null;
pizza = fabrica.criarPizza(tipo); pizza.preparar(); pizza.assar(); pizza.fatiar(); pizza.embalar(); return pizza; }
A nossa pizzaria contará, agora, com uma SimplePizzaFactoryque será responsável pela criação dos objetos.
“...ao encapsular a criação de pizzas em uma
classe, temos apenas um local para fazer
modificações quando a implementação é
alterada...
...uma coisa a ser lembrada é que a
SimplePizzaFactory pode ter muitos clientes...
... alguns desenvolvedores confundem a
Fábrica Simples com o ‘Padrão Fábrica’...
... a Fábrica Simples não é realmente um
Padrão de Projeto... Mas é muito usada...”
Mas o mundo não pára. O negócio continuou crescendo e transformou-se em franchising. Inicialmente são dois franqueados.
Os franqueados querem adaptar o cardápio
e a forma de preparo às preferências de
seus clientes:
• A preferência varia em cada região. O gosto da
população local muda e as franquias querem
oferecer estilos diferentes de pizza. Em um local
a massa deverá ser fina, no outro preferem
massa mais grossa, uns preferem pouco molho
com tempero forte, outros mais molho com
tempero suave, uns querem muito queijo, outros
querem menos queijo, etc...
• O design terá que adaptar-se a seguinte
situação:
os franqueados devem seguir os métodos
assar, fatiar e embalar definidos pelo
franqueador mas terão, cada um, seu
próprio cardápio e forma de preparo da
massa e da cobertura (molho, queijo,
tempero, aplicações, etc.).
Desta forma ao invés de termos:
PizzaMussarela,
PizzaCalabresa,
etc;
Teremos:
PizzaMussarelaSaoPaulo
e
PizzaMussarelaRioDeJaneiro
,
PizzaCalabresaSaoPaulo
e
PizzaCalabresaRioDeJaneiro
,
etc.
Como localizar as atividades de confecção
de pizzas na classe Pizzaria dando aos
franqueados liberdade para ter seu próprio
estilo regional?
Como localizar as atividades de confecção
de pizzas na classe Pizzaria dando aos
franqueados liberdade para ter seu próprio
estilo regional?
• Uma estratégia é devolver o método
criarPizza() para a classe Pizzaria, mas desta
vez como um método
abstrato
, e depois criar
uma subclasse de Pizzaria para cada estilo
regional.
• Quando estudarmos o padrão Abstract Factory
O pedido será, então, definido na superclasse, mas
a criação ficará a critério das subclasses: cada
uma terá a sua própria implementação do método
criarPizza() que será
abstrato na superclasse
.
public
abstract
class Pizzaria {
public Pizza pedirPizza(String
tipo
) {
Pizza pizza = null;
pizza =
criarPizza(
tipo
)
;
pizza.preparar();
pizza.assar();
pizza.fatiar();
pizza.embalar();
return pizza;
}
public
abstract
Pizza
criarPizza(
String
tipo
)
;
}
O método criarPizza()
é um Factory Method!
abstract Produto
factoryMethod(String
tipo)
Um método fábrica é abstrato, de modo que as subclasses têm que lidar com a criação de objetos.
Um método fábrica retorna um produto que geralmente é usado dentro de métodos definidos na superclasse.
Um método fábrica pode ser parametrizado (ou não) para selecionar dentre diversas variações de um produto.
Um método fábrica isola o cliente que não precisa saber que tipo de Produto Concreto é realmente criado.
abstract
Pizza
criarPizza(
String
tipo);
Um método fábrica é abstrato, demodo que as subclasses têm que lidar com a criação de objetos.
Um método fábrica retorna um produto que geralmente é usado dentro de métodos definidos na superclasse.
Um método fábrica pode ser parametrizado (ou não) para selecionar dentre diversas
Um método fábrica isola o cliente que não precisa saber que tipo de Produto Concreto é realmente criado.
public abstract classPizza{ String nome;
String massa; String molho;
ArrayList <String> aplicacoes= new ArrayList<String>(); public voidpreparar(){
System.out.println("Preparando " + nome);
System.out.println("Preparando a massa: " + massa); System.out.println("Adicionando molho: " + molho); System.out.println("Adicionando aplicações: "); for (String aplicacao: aplicacoes) {
System.out.println(“\t" + aplicacao); }
// ... Continuação
public void assar() {
System.out.println("Assando durante 25 minutos a 350º"); }
public void fatiar() {
System.out.println("Cortando a pizza em fatias diagonais"); }
public void embalar() {
System.out.println("Embalando na embalagem padrão"); }
public String getNome() { return nome; }
} // Final
public classPizzaMussarelaSaoPauloextendsPizza{ publicPizzaMussarelaSaoPaulo(){
nome= "Pizza Mussarela"; massa= "Média"; molho= "Marinara";
aplicacoes.add("Mussarela Fresca"); aplicacoes.add("Orégano"); aplicacoes.add("Parmesão");
aplicacoes.add("Azeitona verde com pimentão"); }
}
public classPizzaMussarelaRioDeJaneiroextendsPizza{ publicPizzaMussarelaRioDeJaneiro(){
nome= "Pizza Mussarela"; massa= "Fina Crocante"; molho= "Tomate"; aplicacoes.add("Mussarela"); aplicacoes.add("Orégano"); aplicacoes.add("Azeitona Preta"); } }
public classPizzariaRioDeJaneiro extendsPizzaria {
publicPizza criarPizza(String tipo) { Pizza pizza = null;
if (tipo.equals("Mussarela")) {
pizza = new PizzaMussarelaRioDeJaneiro(); } else if (tipo.equals("Calabresa")) {
pizza = new PizzaCalabresaRioDeJaneiro(); } else if (tipo.equals("Portuguesa")) {
pizza = new PizzaPortuguesaRioDeJaneiro(); } else if (tipo.equals("Napolitana")) {
pizza = new PizzaNapolitanaRioDeJaneiro(); } else if (tipo.equals("Marguerita")) {
pizza = new PizzaMargueritaRioDeJaneiro(); }
return pizza; }
}
public class PizzariaSaoPauloextends Pizzaria {
public Pizza criarPizza(String tipo) { Pizza pizza = null;
if (tipo.equals("Mussarela")) {
pizza = new PizzaMussarelaSaoPaulo(); } else if (tipo.equals("Calabresa")) {
pizza = new PizzaCalabresaSaoPaulo(); } else if (tipo.equals("Portuguesa")) {
pizza = new PizzaPortuguesaSaoPaulo(); } else if (tipo.equals("Napolitana")) {
pizza = new PizzaNapolitanaSaoPaulo(); } else if (tipo.equals("Marguerita")) {
pizza = new PizzaMargueritaSaoPaulo(); }
return pizza; }
}
public classTestaPizzaria {
public static voidmain(String[] args) {
Pizzaria pizzariaSaoPaulo = newPizzariaSaoPaulo(); Pizzaria pizzariaRioDeJaneiro = newPizzariaRioDeJaneiro(); System.out.println("José pediu uma Pizza Mussarela em São Paulo"); Pizza pizza= pizzariaSaoPaulo.pedirPizza("Mussarela");
System.out.println("José recebeu uma "+ pizza.getNome() + ".\n"); System.out.println("Dirceu pediu uma Pizza Mussarela no Rio de
Janeiro"); pizza = pizzariaRioDeJaneiro.pedirPizza("Mussarela");
System.out.println("Dirceu recebeu uma "+ pizza.getNome() + ".\n"); }
José pediu uma Pizza Mussarela em São Paulo
Preparando Pizza Mussarela
Preparando a massa: Média
Adicionando molho: Marinara
Adicionando aplicações:
Mussarela Fresca
Orégano
Parmesão
Azeitona verde com pimentão
Assando durante 25 minutos a 350º
Cortando a pizza em fatias diagonais
Embalando na embalagem padrão.
José recebeu uma Pizza Mussarela.
Saída do Programa
Dirceu pediu uma Pizza Mussarela no Rio de Janeiro
Preparando Pizza Mussarela
Preparando a massa: Fina Crocante
Adicionando molho: Tomate
Adicionando aplicações:
Mussarela
Orégano
Azeitona Preta
Assando durante 25 minutos a 350º
Cortando a pizza em fatias diagonais
Embalando na embalagem padrão.
Dirceu recebeu uma Pizza Mussarela.
Saída do Programa
Factory Method
Intenção
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. [GoF]
Também conhecido como
Virtual Constructor. [GoF]
Factory Method
Aplicabilidade [GoF]
Use o padrão Factory Method quando:
• Uma classe (o criador) 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 entre várias subclasses de apoio e
queremos localizar num ponto único o
conhecimento de qual subclasse está sendo
usada.
Factory Method
Estrutura Genérica
No exemplo visto quem seria Criador,
No exemplo visto quem seria Criador,
CriadorConcreto, Produto e ProdutoConcreto?
Criador:
Pizzaria;
CriadorConcreto:
PizzariaSaoPaulo e PizzariaRioDeJaneiro;
Produto:
Pizza;
ProdutoConcreto:
PizzaMussarelaSaoPaulo,
PizzaMussarelaRioDeJaneiro, etc.
Participantes
http://www.dsc.ufcg.edu.br/~jacques/cursos/map/html/pat/factory.htm
Produto
: define a interface 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 é apenas uma interface mas pode envolver uma classe concreta que tenha uma implementação default para o Factory Method para retornar um objeto com algum tipo ProdutoConcreto default;
• Pode chamar o Factory Method para criar um produto do tipo Produto (método fábrica parametrizado);
Participantes
(Continuação)CriadorConcreto
: faz override do Factory Method para retornar uma instância de ProdutoConcretoColaborações
Criador depende de suas subclasses para definir o Factory Method para que ele retorne uma instância do ProdutoConcreto apropriado
Consequências do uso do padrão Factory Method
http://www.dsc.ufcg.edu.br/~jacques/cursos/map/html/pat/factory.htm
Factory Methods eliminam a necessidade de colocar classes específicas da aplicação no código:
• O código só lida com a interface Produto ;
• O código pode portanto funcionar com qualquer classe ProdutoConcreto;
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
Considerações de implementação
http://www.dsc.ufcg.edu.br/~jacques/cursos/map/html/pat/factory.htm
É boa prática usar uma convenção de nomes para alertar para o fato de que se está usando Factory Methods.
Exemplo: makeAbc(), makeXyz() Exemplo: criaAbc(), criaXyz()
Padrões relacionados [GoF]
Abstract Factory é freqüentemente
implementado utilizando o padrão Factory
Method.
Factory Methods são usualmente chamados
dentro de Template Methods.
Bibliografia
– GAMMA, E., HELM, R., JOHNSON, R. e VLISSIDES, J. Padrões de Projeto – Soluções reutilizáveis de software orientado a obetos. Bookman. 1995; – FREEMAN, Eric, FREEMAN, Elizabeth. Use a Cabeça!
– Padrões de Projeto, Alta Books. 2005;
– LARMAN, Craig. Utilizando UML e Padrões. 2. ed. Bookman. 2002;
– http://www.dsc.ufcg.edu.br/~jacques/cursos/map/html/pat/fact ory.htm;