JavaRush /Blogue Java /Random-PT /Padrões de projeto: FactoryMethod

Padrões de projeto: FactoryMethod

Publicado no grupo Random-PT
Olá! Hoje continuaremos estudando padrões de projeto e falando sobre o método de fábrica (FactoryMethod). Padrões de projeto: FactoryMethod - 1Você 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:
  1. Herança em Java.
  2. 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ê
As coisas estavam indo tão bem para nós que um belo dia absorvemos a preocupação da OneAuto. Como gestores sensatos, não queremos perder clientes OneAuto e a nossa tarefa é reestruturar a produção de forma a podermos produzir:
  • Sedãs AutoRush
  • Carrinhas AutoRush
  • cupê AutoRush
  • Sedãs OneAuto
  • Carrinhas OneAuto
  • cupê OneAuto
Como você pode ver, em vez de um grupo de produtos derivados, surgiram dois, que diferem em alguns detalhes. O padrão de design do método de fábrica resolve o problema de criação de diferentes grupos de produtos, cada um com alguma especificidade. Consideraremos o princípio deste template na prática, passando gradativamente do simples ao complexo, usando o exemplo da nossa cafeteria, que criamos em uma das palestras anteriores .

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.
A única coisa que permanecerá inalterada é o nosso modelo de negócio, que tem se mostrado bem. Se falarmos em linguagem de código, é isso que acontece. Tínhamos 4 classes de produtos:
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:
  1. Vamos retornar o método createCoffee(CoffeeType type)para a classe CoffeeShop.
  2. Vamos tornar esse método abstrato.
  3. A própria classe CoffeeShopse tornará abstrata.
  4. A classe CoffeeShopterá herdeiros.
Sim amigo. Uma cafeteria italiana nada mais é do que uma herdeira da classe 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 Coffeeabstrata. 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 CoffeeShopabstrato, 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. Padrões de projeto: FactoryMethod - 2Que conclusões podem ser tiradas?
  1. Todos os produtos são implementações da classe abstrata Coffee.
  2. Todos os criadores são implementações da classe abstrata CoffeeShop.
  3. Observamos duas hierarquias de classes paralelas:
    • Hierarquia de produtos. Vemos descendentes italianos e descendentes americanos
    • Hierarquia de criadores. Vemos descendentes italianos e descendentes americanos
  4. A superclasse CoffeeShopnão possui informações sobre qual implementação específica do produto ( Coffee) será criada.
  5. Uma superclasse CoffeeShopdelega a criação de um produto específico aos seus descendentes.
  6. Cada classe descendente CoffeeShopimplementa um método de fábrica createCoffee()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.
Agora você está pronto para definir o padrão do método de fábrica . O padrão de método de fábrica define a interface para criação de um objeto, mas permite que as subclasses escolham a classe da instância a ser criada. Assim, o método Factory delega a operação de instanciação às subclasses. Em geral, lembrar a definição não é tão importante quanto compreender como as coisas funcionam.

Estrutura do método de fábrica

Padrões de projeto: FactoryMethod - 3O diagrama acima mostra a estrutura geral do padrão do método de fábrica. O que mais é importante aqui?
  1. A classe Creator contém implementações de todos os métodos que interagem com os produtos, exceto o método fábrica.
  2. Um método abstrato factoryMethod()deve ser implementado por todos os descendentes da classe Creator.
  3. A classe ConcreteCreatorimplementa um método factoryMethod()que produz diretamente um produto.
  4. Esta classe é responsável pela criação de produtos específicos. Esta é a única aula com informações sobre a criação desses produtos.
  5. 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.

Trabalho de casa

Então, hoje trabalhamos bastante e estudamos o padrão de design do método de fábrica. É hora de consolidar o material que você abordou! Tarefa 1. Trabalhe na abertura de outra cafeteria. Pode ser feito em estilo inglês ou espanhol. Ou mesmo no estilo de uma nave espacial. Vamos colocar corante alimentício no café para fazê-lo brilhar e, em geral, o café vai ficar só espaço! Tarefa 2. Na última palestra você teve a tarefa de criar um sushi bar virtual ou uma pizzaria virtual. Sua tarefa é não ficar parado. Hoje você aprendeu como pode usar o padrão de método de fábrica para alcançar o sucesso. Chegou a hora de aproveitar esse conhecimento e expandir seu próprio negócio ;)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION