Oi! Hoje continuaremos a estudar os padrões de projeto e discutiremos o padrão de método de fábrica. Você descobrirá o que é e para quais tarefas esse padrão é adequado. Consideraremos esse padrão de projeto na prática e estudaremos sua estrutura. Para garantir que tudo esteja claro, você precisa entender os seguintes tópicos:
- Herança em Java.
- Métodos abstratos e classes em Java
Que problema o método de fábrica resolve?
Todos os padrões de projeto de fábrica têm dois tipos de participantes: criadores (as próprias fábricas) e produtos (os objetos criados pelas fábricas). Imagine a seguinte situação: temos uma fábrica que produz carros da marca CodeGym. Sabe criar modelos de carros com vários tipos de carrocerias:- sedãs
- peruas
- cupês
- Sedãs CodeGym
- Carrinhas CodeGym
- CodeGym cupês
- sedãs OneAuto
- Carrinhas OneAuto
- OneAuto cupê
Um pouco sobre o padrão de fábrica
Deixe-me lembrá-lo de que construímos anteriormente uma pequena cafeteria virtual. Com a ajuda de uma fábrica simples, aprendemos a fazer diferentes tipos de café. Hoje vamos retrabalhar este exemplo. Vamos relembrar como era nossa cafeteria, com sua fábrica simples. Tivemos uma aula de café:
public class Coffee {
public void grindCoffee(){
// Grind the coffee
}
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
E várias classes infantis correspondentes a tipos específicos de café que nossa fábrica poderia produzir:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Criamos um enum para facilitar a realização de pedidos:
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
A própria fábrica de café era assim:
public class SimpleCoffeeFactory {
public Coffee createCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new Americano();
break;
case ESPRESSO:
coffee = new Espresso();
break;
case CAPPUCCINO:
coffee = new Cappuccino();
break;
case CAFFE_LATTE:
coffee = new CaffeLatte();
break;
}
return coffee;
}
}
E, finalmente, a própria cafeteria ficou assim:
public class CoffeeShop {
private final SimpleCoffeeFactory coffeeFactory;
public CoffeeShop(SimpleCoffeeFactory coffeeFactory) {
this.coffeeFactory = coffeeFactory;
}
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
}
Modernizando uma fábrica simples
Nosso café está funcionando muito bem. Tanto que estamos pensando em expandir. Queremos abrir alguns novos locais. Somos ousados e empreendedores, então não vamos criar cafeterias chatas. Queremos que cada loja tenha um toque especial. Assim, para começar, abriremos duas lojas: uma italiana e outra americana. Essas mudanças afetarão não apenas o design de interiores, mas também as bebidas oferecidas:- na cafeteria italiana, utilizaremos exclusivamente marcas de café italiano, com moagem e torrefação especiais.
- o local americano terá porções maiores e serviremos marshmallows com cada pedido.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Mas agora teremos 8:
public class ItalianStyleAmericano extends Coffee {}
public class ItalianStyleCappucino extends Coffee {}
public class ItalianStyleCaffeLatte extends Coffee {}
public class ItalianStyleEspresso extends Coffee {}
public class AmericanStyleAmericano extends Coffee {}
public class AmericanStyleCappucino extends Coffee {}
public class AmericanStyleCaffeLatte extends Coffee {}
public class AmericanStyleEspresso extends Coffee {}
Como queremos manter o modelo de negócios atual, queremos que o orderCoffee(CoffeeType type)
método sofra o mínimo possível de alterações. Dê uma olhada nisto:
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
Que opções temos? Bem, já sabemos como escrever uma fábrica, certo? A coisa mais simples que vem imediatamente à mente é escrever duas fábricas semelhantes e, em seguida, passar a implementação desejada para o construtor de nossa cafeteria. Ao fazer isso, a classe da cafeteria não mudará. Primeiro, precisamos criar uma nova classe de fábrica, fazer com que ela herde nossa fábrica simples e, em seguida, sobrescrever o createCoffee(CoffeeType type)
método. Vamos escrever fábricas para criar café ao estilo italiano e café ao estilo americano:
public class SimpleItalianCoffeeFactory extends SimpleCoffeeFactory {
@Override
public Coffee createCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new ItalianStyleAmericano();
break;
case ESPRESSO:
coffee = new ItalianStyleEspresso();
break;
case CAPPUCCINO:
coffee = new ItalianStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new ItalianStyleCaffeLatte();
break;
}
return coffee;
}
}
public class SimpleAmericanCoffeeFactory extends SimpleCoffeeFactory{
@Override
public Coffee createCoffee (CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new AmericanStyleAmericano();
break;
case ESPRESSO:
coffee = new AmericanStyleEspresso();
break;
case CAPPUCCINO:
coffee = new AmericanStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new AmericanStyleCaffeLatte();
break;
}
return coffee;
}
}
Agora podemos passar a implementação de fábrica desejada para a CoffeeShop. Vamos ver como seria o código para pedir café em diferentes cafeterias. Por exemplo, cappuccino estilo italiano e estilo americano:
public class Main {
public static void main(String[] args) {
/*
Order an Italian-style cappuccino:
1. Create a factory for making Italian coffee
2. Create a new coffee shop, passing the Italian coffee factory to it through the constructor
3. Order our coffee
*/
SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
/*
Order an American-style cappuccino
1. Create a factory for making American coffee
2. Create a new coffee shop, passing the American coffee factory to it through the constructor
3. Order our coffee
*/
SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
}
}
Criamos duas cafeterias diferentes, passando a fábrica desejada para cada uma. Por um lado, atingimos nosso objetivo, mas, por outro lado... De alguma forma, isso não agrada aos empresários... Vamos descobrir o que está errado. Primeiro, a abundância de fábricas. O que? Agora, para cada novo local, devemos criar sua própria fábrica e, além disso, garantir que a fábrica relevante seja passada para o construtor ao criar uma cafeteria? Em segundo lugar, ainda é uma fábrica simples. Apenas modernizado ligeiramente. Mas estamos aqui para aprender um novo padrão. Em terceiro lugar, não é possível uma abordagem diferente? Seria ótimo se pudéssemos colocar todas as questões relacionadas ao preparo do café noCoffeeShop
classe, vinculando os processos de criação de café e pedidos de atendimento, mantendo simultaneamente flexibilidade suficiente para fazer vários estilos de café. A resposta é sim, podemos. Isso é chamado de padrão de design do método de fábrica.
De uma fábrica simples para um método de fábrica
Para resolver a tarefa da forma mais eficiente possível:- Retornamos o
createCoffee(CoffeeType type)
método para aCoffeeShop
classe. - Faremos esse método abstrato.
- A
CoffeeShop
própria classe se tornará abstrata. - A
CoffeeShop
turma terá turmas filhas.
CoffeeShop
classe, que implementa o createCoffee(CoffeeType type)
método de acordo com as melhores tradições dos baristas italianos. Agora, um passo de cada vez. Passo 1. Torne a Coffee
classe abstrata. Temos duas famílias inteiras de produtos diferentes. Ainda assim, os cafés italiano e americano têm um ancestral comum – a Coffee
classe. Seria apropriado torná-lo abstrato:
public abstract class Coffee {
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Passo 2. Faça CoffeeShop
abstrato, com um createCoffee(CoffeeType type)
método abstrato
public abstract class CoffeeShop {
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = createCoffee(type);
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
protected abstract Coffee createCoffee(CoffeeType type);
}
Etapa 3. Crie uma cafeteria italiana, descendente da cafeteria abstrata. Implementamos o createCoffee(CoffeeType type)
método nele, levando em consideração as especificidades das receitas italianas.
public class ItalianCoffeeShop extends CoffeeShop {
@Override
public Coffee createCoffee (CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new ItalianStyleAmericano();
break;
case ESPRESSO:
coffee = new ItalianStyleEspresso();
break;
case CAPPUCCINO:
coffee = new ItalianStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new ItalianStyleCaffeLatte();
break;
}
return coffee;
}
}
Etapa 4. Fazemos o mesmo para a cafeteria estilo americano
public class AmericanCoffeeShop extends CoffeeShop {
@Override
public Coffee createCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new AmericanStyleAmericano();
break;
case ESPRESSO:
coffee = new AmericanStyleEspresso();
break;
case CAPPUCCINO:
coffee = new AmericanStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new AmericanStyleCaffeLatte();
break;
}
return coffee;
}
}
Passo 5. Confira como ficarão os lattes americanos e italianos:
public class Main {
public static void main(String[] args) {
CoffeeShop italianCoffeeShop = new ItalianCoffeeShop();
italianCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);
CoffeeShop americanCoffeeShop = new AmericanCoffeeShop();
americanCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);
}
}
Parabéns. Acabamos de implementar o padrão de design do método de fábrica usando nossa cafeteria como exemplo.
O princípio por trás dos métodos de fábrica
Agora vamos considerar com mais detalhes o que obtivemos. O diagrama abaixo mostra as classes resultantes. Os blocos verdes são classes de criadores e os blocos azuis são classes de produtos. Que conclusões podemos tirar?- Todos os produtos são implementações da
Coffee
classe abstrata. - Todos os criadores são implementações da
CoffeeShop
classe abstrata. - Vemos duas hierarquias de classes paralelas:
- Hierarquia de produtos. Vemos descendentes de italianos e descendentes de americanos
- Hierarquia dos criadores. Vemos descendentes de italianos e descendentes de americanos
- A
CoffeeShop
superclasse não possui informações sobre qual produto específico (Coffee
) será criado. - A
CoffeeShop
superclasse delega a criação de um produto específico para seus descendentes. - Cada descendente da
CoffeeShop
classe implementa umcreateCoffee()
método fábrica de acordo com suas especificidades. Em outras palavras, as implementações das classes produtoras preparam produtos específicos com base nas especificidades da classe produtora.
Estrutura de um método fábrica
O diagrama acima mostra a estrutura geral do padrão de método de fábrica. O que mais é importante aqui?- A classe Creator implementa todos os métodos que interagem com produtos, exceto o método fábrica.
- O método abstrato
factoryMethod()
deve ser implementado por todos os descendentes daCreator
classe. - A
ConcreteCreator
classe implementa ofactoryMethod()
método, que cria diretamente o produto. - Esta classe é responsável por criar produtos específicos. Esta é a única aula com informações sobre como criar esses produtos.
- Todos os produtos devem implementar uma interface comum, ou seja, devem ser descendentes de uma classe de produto comum. Isso é necessário para que classes que usam produtos possam operar neles como abstrações, ao invés de implementações específicas.
GO TO FULL VERSION