Hi, friend! Today we will continue to study design patterns. In this lesson, we're going to talk about factories. We will discuss the problem that this pattern solves and look at an example of how a factory can help you open a coffee shop. Additionally, I will give you 5 simple steps to create a factory.
To make sure that we're all on the same wavelength and that you'll quickly grasp this concept, you should be familiar with the following topics:
- Inheritance in Java
- Narrowing and widening of reference types in Java
- Interaction between different classes and objects.
What is a factory?
The factory design pattern lets you control the creation of objects. The process of creating a new object is not super simple, but neither is it overly complicated. We all know that we need thenew
operator to create a new object. Perhaps it seems that there is nothing to control here, but that is not true.
Suppose our application has a certain class that has many descendants. Difficulties can arise when it is necessary to create an instance of a specific class depending on certain conditions.
A factory is a design pattern that helps solve the problem of creating various objects depending on certain conditions.
How's that for an abstract concept? This will get clearer and more specific when we look at the example below.
Let's prepare various types of coffee
Suppose we want to automate a coffee shop. We need to teach our program how to make different types of coffee. To do this, we will create a coffee class and a few derivative classes to represent the types of coffee that we will prepare: Americano, cappuccino, espresso, and latte. Let's start with a general coffee class:
public class Coffee {
public void grindCoffee(){
// Grind the coffee
}
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Next, we'll create its child classes:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Our customers can order any type of coffee. Their orders need to be passed to the program. This can be done in many ways, for example, using String
. But an enum
is best for this. We'll create an enum
and define enum fields that correspond to the types of coffee that can be ordered:
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
Great. Now let's write the code for our coffee shop:
public class CoffeeShop {
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new Americano();
break;
case ESPRESSO:
coffee = new Espresso();
break;
case CAPPUCCINO:
coffee = new Cappucсino();
break;
case CAFFE_LATTE:
coffee = new CaffeLatte();
break;
}
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
}
The orderCoffee
method can be divided into two parts:
- Creation of a specific instance of coffee in a
switch
statement. This is where a factory does what it does — create a specific type depending on conditions. - Preparation — this is the grinding, brewing, and pouring into a cup.
- The steps involved in the preparation itself (grinding, brewing, and pouring into a cup) will remain unchanged (at least we are counting on this).
- But the assortment of coffees may change. Maybe we'll start making mocha... Frappu... Mochacci... Whatever, a new kind of coffee.
switch
statement.
It's also possible that in our coffee shop the orderCoffee
method will not be the only place where we will create different types of coffee. As a result, changes will have to be made in several places.
You probably already understand what I'm getting at. We need to refactor. Move the block responsible for creating coffee into a separate class for two reasons:
- We can reuse the coffee-making logic in other places.
- If the assortment changes, we won't have to edit the code everywhere coffee is created. It will be enough to change our code in just one place.
Setting up our first factory
To do this, we'll create a new class that will only be responsible for creating the necessary instances of coffee classes:
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 Cappucino();
break;
case CAFFE_LATTE:
coffee = new CaffeLatte();
break;
}
return coffee;
}
}
Congratulations! We have just implemented the factory design pattern in its simplest form (almost).
It could have been even simpler if we made the createCoffee
method static. But then we would lose two capabilities:
- The ability to inherit
SimpleCoffeeFactory
and override thecreateCoffee
method. - The ability to add the required factory implementation to our classes.
Adding a factory to the coffee shop
Let's rewrite the coffee shop class using a factory:
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;
}
}
Excellent. Now we'll provide a concise description of the general structure of the factory design pattern.
5 steps to opening your own factory
Step 1. Your program has a class with several descendants, as in the diagram below: Step 2. You create anenum
with a field for each child class:
enum CatType {
LION,
TIGER,
FLUFFY
}
Step 3. Build your factory. Call it CatFactory
. Here's the code:
class CatFactory {}
Step 4. In your factory, create a createCat
method that takes a CatType
argument. Here's the code:
class CatFactory {
public Cat createCat(CatType type) {
}
}
Step 5. In the body of the method, write a switch
statement that enumerates the enum fields and creates an instance of the class that corresponds to the passed enum
value:
class CatFactory {
public Cat createCat(CatType type) {
Cat cat = null;
switch (type) {
case LION:
cat = new Fluffy();
break;
case TIGER:
cat = new Tiger();
break;
case FLUFFY:
cat = new Lion();
break;
}
return cat;
}
}
Now you can run a factory like a boss. :)
GO TO FULL VERSION