JavaRush /Java Blog /Random EN /Design Patterns: FactoryMethod

Design Patterns: FactoryMethod

Published in the Random EN group
Hello! Today we will continue to study design patterns and talk about the factory method (FactoryMethod). Design Patterns: FactoryMethod - 1You will find out what it is and what tasks this template is suitable for. We'll look at this design pattern in practice and explore its structure. To make all of the above clear to you, you need to understand the following topics:
  1. Inheritance in Java.
  2. Abstract methods and classes in Java.

What problem does the factory method solve?

In all factory design patterns, there are two groups of participants - creators (the factories themselves) and products (the objects created by the factories). Imagine the situation: we have a factory that produces cars under the AutoRush brand. She knows how to create car models with different types of bodies:
  • sedans
  • station wagons
  • coupe
Things were going so well for us that one fine day we absorbed the OneAuto concern. As sensible managers, we do not want to lose OneAuto customers, and our task is to restructure production in such a way that we can produce:
  • AutoRush sedans
  • AutoRush station wagons
  • coupe AutoRush
  • OneAuto sedans
  • OneAuto station wagons
  • OneAuto coupe
As you can see, instead of one group of derivative products, two appeared, which differ in some details. The factory method design pattern solves the problem of creating different groups of products, each with some specificity. We will consider the principle of this template in practice, gradually moving from simple to complex, using the example of our coffee shop, which we created in one of the previous lectures .

A little about the factory template

Let me remind you: we built a small virtual coffee shop with you. In it, we learned how to create different types of coffee using a simple factory. Today we will refine this example. Let's remember what our coffee shop with a simple factory looked like. We had a coffee class:
public class Coffee {
    public void grindCoffee(){
        // перемалываем кофе
    }
    public void makeCoffee(){
        // делаем кофе
    }
    public void pourIntoCup(){
        // наливаем в чашку
    }
}
And also several of his heirs - specific types of coffee that our factory could produce:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
For the convenience of accepting orders, we have introduced transfers:
public enum CoffeeType {
    ESPRESSO,
    AMERICANO,
    CAFFE_LATTE,
    CAPPUCCINO
}
The coffee factory itself looked like this:
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;
    }
}
And finally, the coffee shop itself:
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;
    }
}

Modernization of a simple factory

Our coffee shop is doing well. So much so that we are thinking about expanding. We want to open several new points. As enterprising guys, we will not churn out monotonous coffee shops. I want each one to have its own twist. Therefore, to begin with, we will open two points: in Italian and American styles. The changes will affect not only the interior, but also drinks:
  • in an Italian coffee shop we will use exclusively Italian coffee brands, with special grinding and roasting.
  • The American portion will be a little larger, and with each order we will serve melted marshmallows - marshmallows.
The only thing that will remain unchanged is our business model, which has proven itself well. If we speak in code language, this is what happens. We had 4 classes of products:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
And it becomes 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 {}
Since we want to keep the current business model unchanged, we want the method orderCoffee(CoffeeType type)to undergo a minimum number of changes. Let's take a look at it:
public Coffee orderCoffee(CoffeeType type) {
    Coffee coffee = coffeeFactory.createCoffee(type);
    coffee.grindCoffee();
    coffee.makeCoffee();
    coffee.pourIntoCup();

    System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
    return coffee;
}
What options do we have? We already know how to write a factory, right? The simplest thing that immediately comes to mind is to write two similar factories, and then pass the required implementation to our coffee shop in the constructor. Then the class of the coffee shop will not change. First, we need to create a new factory class, inherit from our simple factory and override the createCoffee (CoffeeType type). Let's write factories for creating coffee in Italian and American styles:
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;
    }

}
Now we can pass the required factory implementation to CoffeeShop. Let's see what the code for ordering coffee from different coffee shops would look like. For example, cappuccino in Italian and American styles:
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);
    }
}
We created two different coffee shops, transferring each to the required factory. On the one hand, we have achieved our goal, but on the other hand... Something is scratching the irrepressible soul of the entrepreneur... Let's figure out what's wrong. Firstly, the abundance of factories. Is it possible to create your own factory every time for a new point and, in addition, make sure that when creating a coffee shop, the required factory is transferred to the constructor? Secondly, it is still a simple factory. Just a little modernized. We are still studying a new pattern here. Thirdly, isn’t it possible to do it differently? It would be cool if we could localize all the questions about making coffee inside the classroom CoffeeShop, linking the processes of creating coffee and servicing the order, but at the same time maintaining enough flexibility to make coffee in different styles. The answer is yes, you can. This is called the factory method design pattern.

From a simple factory to a factory method

To solve the problem as efficiently as possible, we:
  1. Let's return the method createCoffee(CoffeeType type)to the class CoffeeShop.
  2. Let's make this method abstract.
  3. The class itself CoffeeShopwill become abstract.
  4. The class CoffeeShopwill have heirs.
Yes, friend. An Italian coffee shop is nothing more than a heir to the class CoffeeShop, implementing a method createCoffee(CoffeeType type)in accordance with the best traditions of Italian baristas. So, in order. Step 1. Let's make the class Coffeeabstract. We now have two families of different products. Italian and American coffee drinks still share a common ancestor: the Coffee. It would be correct to make it abstract:
public abstract class Coffee {
    public void makeCoffee(){
        // делаем кофе
    }
    public void pourIntoCup(){
        // наливаем в чашку
    }
}
Step 2. Make it CoffeeShopabstract, with an abstract methodcreateCoffee(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);
}
Step 3. Create an Italian coffee shop, a descendant class of the abstract coffee shop. In it we implement the method createCoffee(CoffeeType type)taking into account Italian specifics.
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;
    }
}
Step 4. Let's do the same for an American-style coffee shop.
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;
    }
}
Step 5. Let's take a look at what ordering an American and Italian style latte would look like:
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);
    }
}
Congratulations. We just implemented the factory method design pattern in our coffee shop.

How the factory method works

Now let's take a closer look at what we got. The diagram below shows the resulting classes. Green blocks are creator classes, blue blocks are product classes. Design Patterns: FactoryMethod - 2What conclusions can be drawn?
  1. All products are implementations of the abstract class Coffee.
  2. All creators are implementations of the abstract class CoffeeShop.
  3. We observe two parallel class hierarchies:
    • Hierarchy of products. We see Italian descendants and American descendants
    • Hierarchy of creators. We see Italian descendants and American descendants
  4. The superclass CoffeeShophas no information about which specific product implementation ( Coffee) will be created.
  5. A superclass CoffeeShopdelegates the creation of a specific product to its descendants.
  6. Each descendant class CoffeeShopimplements a factory method createCoffee()in accordance with its specifics. In other words, within the implementations of creator classes, a decision is made to prepare a specific product based on the specifics of the creator class.
Now you are ready to define the factory method pattern . The factory method pattern defines the interface for creating an object, but allows subclasses to choose the class of the instance to create. Thus, the Factory method delegates the instantiation operation to subclasses. In general, remembering the definition is not as important as understanding how things work.

Factory method structure

Design Patterns: FactoryMethod - 3The diagram above shows the general structure of the factory method pattern. What else is important here?
  1. The Creator class contains implementations of all methods that interact with products, except for the factory method.
  2. An abstract method factoryMethod()must be implemented by all descendants of the class Creator.
  3. The class ConcreteCreatorimplements a method factoryMethod()that directly produces a product.
  4. This class is responsible for creating specific products. This is the only class with information about creating these products.
  5. All products must implement a common interface - be descendants of a common product class. This is necessary so that classes that use products can operate on them at the level of abstractions rather than concrete implementations.

Homework

So, today we did quite a lot of work and studied the factory method design pattern. It's time to consolidate the material you've covered! Task 1. Work on opening another coffee shop. It can be made in English style or Spanish. Or even in the style of a spaceship. Let's add food coloring to the coffee to make it shine, and in general, the coffee will be just space! Task 2. At the last lecture, you had the task of creating a virtual sushi bar or a virtual pizzeria. Your task is not to stand still. Today you learned how you can use the factory method pattern to achieve success. It's time to take advantage of this knowledge and expand your own business ;)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION