Hi! Today we will continue to study design patterns and we'll discuss the factory method pattern. Design patterns: Factory method - 1 You will find out what it is and what tasks this pattern is suitable for. We will consider this design pattern in practice and study its structure. To ensure everything is clear, 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?

All factory design patterns have two types of participants: creators (the factories themselves) and products (the objects created by the factories). Imagine the following situation: we have a factory that produces CodeGym-branded cars. It knows how to create models of cars with various types of bodies:
  • sedans
  • station wagons
  • coupes
Our business prospered so much that one fine day we acquired another carmaker — OneAuto. Being sensible business owners, we don't want to lose any OneAuto customers, and so we are faced with the task of restructuring production so that we can produce:
  • CodeGym sedans
  • CodeGym station wagons
  • CodeGym coupes
  • OneAuto sedans
  • OneAuto station wagons
  • OneAuto coupes
As you can see, instead of one group of products, we now have two, and they differ in certain details. The factory method design pattern is for when we need to create different groups of products, each of which has some specific traits. We will consider this pattern's guiding principle in practice, gradually moving from the simple to the complex, using the example of our coffee shop, which we created in one of the previous lessons.

A bit about the factory pattern

Let me remind you that we previously built a small virtual coffee shop. With the help of a simple factory, we learned how to create different types of coffee. Today we will rework this example. Let's recall how our coffee shop looked, with its simple factory. We had a coffee class:
public class Coffee {
    public void grindCoffee(){
        // Grind the coffee
    }
    public void makeCoffee(){
        // Brew the coffee
    }
    public void pourIntoCup(){
        // Pour into a cup
    }
}
And several child classes corresponding to 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 {}
We created an enum to make it easy to place orders:
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 looked like this:
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;
    }
}

Modernizing a simple factory

Our coffee shop is running very well. So much so that we are considering expanding. We want to open some new locations. We're bold and enterprising, so we won't crank out boring coffee shops. We want each shop to have a special twist. Accordingly, to begin with, we'll open two locations: one Italian and one American. These changes will affect not only the interior design, but also the drinks offered:
  • in the Italian coffee shop, we will use exclusively Italian coffee brands, with special grinding and roasting.
  • the American location will have larger portions, and we'll serve marshmallows with each order.
The only thing that remains unchanged is our business model, which has proven itself to be excellent. In terms of the code, this is what happens. We had 4 classes corresponding to our products:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
But now we will have 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, we want the orderCoffee(CoffeeType type) method to undergo as few changes as possible. Take a look at it:
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;
}
What options do we have? Well, 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 desired implementation to our coffee shop's constructor. By doing this, the coffee shop's class will not change. First, we need to create a new factory class, make it inherit our simple factory, and then override the createCoffee(CoffeeType type) method. Let's write factories for creating Italian-style coffee and American-style coffee:
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 desired factory implementation to CoffeeShop. Let's see what the code for ordering coffee from different coffee shops would look like. For example, Italian-style and American-style cappuccino:
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
         */
        SimpleItalianCoffeeFactory americanCoffeeFactory = new SimpleItalianCoffeeFactory();
        CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
        americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
    }
}
We created two different coffee shops, passing the desired factory to each. On the one hand, we have accomplished our objective, but on the other hand... Somehow this doesn't sit well with the entrepreneurs... Let's figure out what is wrong. First, the abundance of factories. What? Now for every new location, we're supposed to create its own factory and, in addition to that, make sure that the relevant factory is passed to the constructor when creating a coffee shop? Second, it is still a simple factory. Just modernized slightly. But we're here to learn a new pattern. Third, isn't a different approach possible? It would be great if we could put all issues related to coffee preparation into the CoffeeShop class by linking the processes of creating coffee and servicing orders, while simultaneously maintaining sufficient flexibility to make various styles of coffee. The answer is yes, we can. This is called the factory method design pattern.

From a simple factory to a factory method

To solve the task as efficiently as possible:
  1. We return the createCoffee(CoffeeType type) method to the CoffeeShop class.
  2. We will make this method abstract.
  3. The CoffeeShop class itself will become abstract.
  4. The CoffeeShop class will have child classes.
Yes, friend. The Italian coffee shop is nothing more than a descendant of the CoffeeShop class, which implements the createCoffee(CoffeeType type) method in accordance with the best traditions of Italian baristas. Now, one step at a time. Step 1. Make the Coffee class abstract. We have two whole families of different products. Still, the Italian and American coffees have a common ancestor — the Coffee class. It would be proper to make it abstract:
public abstract class Coffee {
    public void makeCoffee(){
        // Brew the coffee
    }
    public void pourIntoCup(){
        // Pour into a cup
    }
}
Step 2. Make CoffeeShop abstract, with an abstract createCoffee(CoffeeType type) method
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);
}
Step 3. Create an Italian coffee shop, which is a descendant of the abstract coffee shop. We implement the createCoffee(CoffeeType type) method in it, taking into account the specifics of Italian recipies.
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. We do the same for the 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. Check out how American and Italian lattes will look:
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 using our coffee shop as an example.

The principle behind factory methods

Now let's consider in greater detail what we got. The diagram below shows the resulting classes. The green blocks are creator classes, and the blue blocks are product classes. Design patterns: Factory method - 2What conclusions can we make?
  1. All products are implementations of the abstract Coffee class.
  2. All creators are implementations of the abstract CoffeeShop class.
  3. We see 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 CoffeeShop superclass has no information about which specific product (Coffee) will be created.
  5. The CoffeeShop superclass delegates the creation of a specific product to its descendants.
  6. Each descendant of the CoffeeShop class implements a createCoffee() factory method in accordance with its own specifics features. In other words, the implementations of the producer classes prepare specific products based on the specifics of the producer class.
You are now ready for the definition of the factory method pattern. The factory method pattern defines an interface for creating an object, but allows subclasses to select the class of the created object. Thus, a factory method delegates creation of an instance to subclasses. In general, remembering the definition is not as important as understanding how it all works.

Structure of a factory method

Design patterns: Factory method - 3The diagram above shows the general structure of the factory method pattern. What else is important here?
  1. The Creator class implements all methods that interact with products, except the factory method.
  2. The abstract factoryMethod() method must be implemented by all descendants of the Creator class.
  3. The ConcreteCreator class implements the factoryMethod() method, which directly creates the 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, i.e. they must be descendants of a common product class. This is necessary so that classes that use products can operate on them as abstractions, rather than specific implementations.
Design patterns: Factory method - 4

Homework

Today we have done quite a lot of work and studied the factory method design pattern. It's time to reinforce the material! Exercise 1. Do the work to open another coffee shop. It could be an English-style or Spanish-style coffee shop. Or even spaceship-style. Add food coloring to the coffee to make it glow, and your coffee will be simply out of this world! Exercise 2. In the last lesson, you had an exercise where you created a virtual sushi bar or a virtual pizzeria. Now your exercise is to not stand still. Today you learned how to use the factory method pattern to your advantage. It's time to use this knowledge and expand your own business ;)