Olá! Hoje continuaremos estudando padrões de projeto e falando sobre o método de fábrica (FactoryMethod). Você descobrirá o que é e para quais tarefas este modelo é adequado. Veremos esse padrão de design na prática e exploraremos sua estrutura. Para deixar tudo isso claro para você, 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?
Em todos os padrões de design de fábrica, existem dois grupos de participantes – criadores (as próprias fábricas) e produtos (os objetos criados pelas fábricas). Imagine a situação: temos uma fábrica que produz carros com a marca AutoRush. Ela pode criar modelos de carros com diferentes tipos de carrocerias:- sedãs
- peruas
- cupê
- Sedãs AutoRush
- Carrinhas AutoRush
- cupê AutoRush
- Sedãs OneAuto
- Carrinhas OneAuto
- cupê OneAuto
Um pouco sobre o template de fábrica
Deixe-me lembrá-lo: construímos uma pequena cafeteria virtual com você. Nele aprendemos como criar diferentes tipos de café utilizando uma fábrica simples. Hoje vamos refinar este exemplo. Vamos relembrar como era nossa cafeteria com uma fábrica simples. Tivemos uma aula de café:public class Coffee {
public void grindCoffee(){
// перемалываем кофе
}
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
E também vários de seus herdeiros - 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 {}
Para maior comodidade na aceitação de encomendas, introduzimos as transferências:
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 por fim, a cafeteria em si:
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("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
}
Modernização de uma fábrica simples
Nossa cafeteria está indo bem. Tanto que estamos pensando em expandir. Queremos abrir vários novos pontos. Como empreendedores, não produziremos cafeterias monótonas. Quero que cada um tenha seu próprio toque. Portanto, para começar, abriremos dois pontos: nos estilos italiano e americano. As mudanças afetarão não só o interior, mas também as bebidas:- em uma cafeteria italiana utilizaremos cafés exclusivamente de marcas italianas, com moagem e torra especiais.
- A porção americana será um pouco maior, e a cada pedido serviremos marshmallows derretidos - marshmallows.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
E se torna 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 inalterado o modelo de negócio atual, queremos que o método orderCoffee(CoffeeType type)
sofra um número mínimo de alterações. Vamos dar uma olhada nisso:
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
Que opções temos? Já sabemos escrever uma fábrica, certo? A coisa mais simples que vem imediatamente à mente é escrever duas fábricas semelhantes e depois passar a implementação necessária para nossa cafeteria no construtor. Então a classe da cafeteria não mudará. Primeiro, precisamos criar uma nova classe de fábrica, herdar de nossa fábrica simples e substituir o arquivo createCoffee (CoffeeType type)
. Vamos escrever fábricas para criar café nos estilos italiano e 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 necessária para o CoffeeShop. Vamos ver como seria o código para pedir café em diferentes cafeterias. Por exemplo, cappuccino nos estilos italiano e americano:
public class Main {
public static void main(String[] args) {
/*
Закажем капучино в итальянском стиле:
1. Создадим фабрику для приготовления итальянского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику итальянского кофе
3. Закажем наш кофе
*/
SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
/*
Закажем капучино в американском стиле
1. Создадим фабрику для приготовления американского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику американского кофе
3. Закажем наш кофе
*/
SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
}
}
Criamos duas cafeterias diferentes, transferindo cada uma para a fábrica necessária. Por um lado, atingimos o nosso objetivo, mas por outro lado... Algo está arranhando a alma irreprimível do empreendedor... Vamos descobrir o que há de errado. Em primeiro lugar, a abundância de fábricas. É possível criar sempre a sua própria fábrica para um novo ponto e, além disso, garantir que ao criar uma cafetaria a fábrica necessária seja transferida para o construtor? Em segundo lugar, ainda é uma fábrica simples. Apenas um pouco modernizado. Ainda estamos estudando um novo padrão aqui. Terceiro, não é possível fazer diferente? Seria legal se pudéssemos localizar todas as questões sobre como fazer café dentro da sala de aula CoffeeShop
, interligando os processos de criação do café e atendimento do pedido, mas ao mesmo tempo mantendo flexibilidade suficiente para fazer café em diferentes estilos. A resposta é sim você pode. Isso é chamado de padrão de design do método de fábrica.
De uma fábrica simples a um método de fábrica
Para resolver o problema da forma mais eficiente possível, nós:- Vamos retornar o método
createCoffee(CoffeeType type)
para a classeCoffeeShop
. - Vamos tornar esse método abstrato.
- A própria classe
CoffeeShop
se tornará abstrata. - A classe
CoffeeShop
terá herdeiros.
CoffeeShop
, implementando um método createCoffee(CoffeeType type)
de acordo com as melhores tradições dos baristas italianos. Então, em ordem. Passo 1. Vamos tornar a classe Coffee
abstrata. Agora temos duas famílias de produtos diferentes. As bebidas de café italianas e americanas ainda compartilham um ancestral comum: o Coffee
. Seria correto torná-lo abstrato:
public abstract class Coffee {
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
Passo 2. Torne-o CoffeeShop
abstrato, com um método abstratocreateCoffee(CoffeeType type)
public abstract class CoffeeShop {
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = createCoffee(type);
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
protected abstract Coffee createCoffee(CoffeeType type);
}
Passo 3. Crie uma cafeteria italiana, uma classe descendente da cafeteria abstrata. Nele implementamos o método createCoffee(CoffeeType type)
levando em consideração as especificidades 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;
}
}
Passo 4. Vamos fazer o mesmo para uma 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;
}
}
Etapa 5. Vamos dar uma olhada em como seria pedir um café com leite estilo americano e italiano:
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 em nossa cafeteria.
Como funciona o método de fábrica
Agora vamos dar uma olhada mais de perto no que obtivemos. O diagrama abaixo mostra as classes resultantes. Os blocos verdes são classes de criadores, os blocos azuis são classes de produtos. Que conclusões podem ser tiradas?- Todos os produtos são implementações da classe abstrata
Coffee
. - Todos os criadores são implementações da classe abstrata
CoffeeShop
. - Observamos duas hierarquias de classes paralelas:
- Hierarquia de produtos. Vemos descendentes italianos e descendentes americanos
- Hierarquia de criadores. Vemos descendentes italianos e descendentes americanos
- A superclasse
CoffeeShop
não possui informações sobre qual implementação específica do produto (Coffee
) será criada. - Uma superclasse
CoffeeShop
delega a criação de um produto específico aos seus descendentes. - Cada classe descendente
CoffeeShop
implementa um método de fábricacreateCoffee()
de acordo com suas especificidades. Em outras palavras, dentro das implementações das classes criadoras, é tomada a decisão de preparar um produto específico com base nas especificidades da classe criadora.
Estrutura do método de fábrica
O diagrama acima mostra a estrutura geral do padrão do método de fábrica. O que mais é importante aqui?- A classe Creator contém implementações de todos os métodos que interagem com os produtos, exceto o método fábrica.
- Um método abstrato
factoryMethod()
deve ser implementado por todos os descendentes da classeCreator
. - A classe
ConcreteCreator
implementa um métodofactoryMethod()
que produz diretamente um produto. - Esta classe é responsável pela criação de produtos específicos. Esta é a única aula com informações sobre a criação desses produtos.
- Todos os produtos devem implementar uma interface comum – ser descendentes de uma classe de produto comum. Isso é necessário para que as classes que utilizam produtos possam operar neles no nível de abstrações, em vez de implementações concretas.
GO TO FULL VERSION