Hi, friend! Today we will continue to study design patterns with you. In this lecture we will talk about the Factory. We’ll discuss with you what problem is solved using this template, and look at an example of how a factory helps open a coffee shop. And I will also give you 5 simple steps to create a factory. To be on the same page with everyone and easily grasp the essence, you should be familiar with the following topics:
- Inheritance in Java
- Narrowing and expanding reference types in Java
- Interaction between different classes and objects
What is a Factory?
The Factory design pattern allows you to control the creation of objects. The process of creating a new object is not that simple, but it is not too complicated either. We all know that to create a new object we must use thenew
. And it may seem that there is nothing to manage here, but this is not so. Difficulties may arise when our application has a certain class that has many descendants, and it is necessary to create an instance of a certain class depending on some conditions. Factory is a design pattern that helps solve the problem of creating different objects depending on some conditions. Abstract, isn't it? More specificity and clarity will appear when we look at the example below.
We create different types of coffee
Let's say we want to automate a coffee shop. We need to learn how to prepare different types of coffee. To do this, in our application we will create a coffee class and its derivatives: Americano, cappuccino, espresso, latte - those types of coffee that we will prepare. Let's start with the general coffee class:public class Coffee {
public void grindCoffee(){
// перемалываем кофе
}
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
Next, let's create its heirs:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Our customers will order some type of coffee, and this information needs to be passed to the program. This can be done in different ways, for example using String
. But it is best suited for these purposes enum
. Let's create enum
and define in it the types of coffee for which we accept orders:
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("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
}
The method orderCoffee
can be divided into two components:
- Creating a specific coffee instance in a block
switch-case
. This is where what the Factory does is the creation of a specific type depending on the conditions. - The preparation itself is grinding, cooking and pouring into a cup.
- The preparation algorithm itself (grinding, cooking and pouring into a cup) will remain unchanged (at least we hope so).
- But the range of coffee may change. Maybe we'll start making mocha.. Mocha.. Mokkachi... God bless him, a new type of coffee.
switch-case
. It is also possible that in our coffee shop the method orderCoffee
will not be the only place in which we create different types of coffee. Therefore, 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 will be able to reuse the logic of creating coffee in other places.
- If the range changes, we will not have to edit the code everywhere where coffee creation will be used. It will be enough to change the code in only one place.
We are sawing our first factory
To do this, let's create a new class that will be responsible only 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've just implemented the Factory design pattern in its simplest form. Although everything could be even simpler if the method was made createCoffee
static. But then we would lose two possibilities:
- Inherit from
SimpleCoffeeFactory
and override thecreateCoffee
. - Implement the required factory implementation in our classes.
Introduction of a factory into a coffee shop
Let's rewrite our 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("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
}
Great. Now let’s try to schematically and concisely describe the structure of the Factory design pattern.
5 steps to opening your own factory
Step 1. In your program you have a class with several descendants, as in the picture below: Step 2. You create a classenum
in which you define an enum variable for each descendant class:
enum CatType {
LION,
TIGER,
BARSIK
}
Step 3. You build your factory. You call it MyClassFactory
, the code is below:
class CatFactory {}
Step 4. You create a method in your factory createMyClass
that takes the variable - enum
MyClassType
. Code below:
class CatFactory {
public Cat createCat(CatType type) {
}
}
Step 5. You write a block in the body of the method switch-case
in which you iterate through all enum values and create an instance of the class corresponding to enum
the value:
class CatFactory {
public Cat createCat(CatType type) {
Cat cat = null;
switch (type) {
case LION:
cat = new Barsik();
break;
case TIGER:
cat = new Tiger();
break;
case BARSIK:
cat = new Lion();
break;
}
return cat;
}
}
Like a boss.
GO TO FULL VERSION