Привет, друг! Сегодня мы продолжим изучать с тобой паттерны проектирования. В этой лекции будем говорить о Фабрике. Обсудим с тобой, какую проблему решают с помощью данного шаблона, посмотрим на примере, как фабрика помогает открывать кофейню. А еще я я дам тебе 5 простых шагов для создания фабрики. Паттерн проектирования Factory - 1Чтобы быть со всеми на одной волне и легко улавливать суть, тебе должны быть знакомы такие темы:
  • Наследование в Java
  • Сужение и расширение ссылочных типов в Java
  • Взаимодействие между различными классами и объектами

Что такое Фабрика?

Шаблон проектирования Фабрика позволяет управлять созданием объектов. Процесс создания нового объекта не то чтобы прост, но и не слишком сложен. Все мы знаем, что для создания нового объекта необходимо использовать оператор new. И может показаться, что здесь нечем управлять, однако это не так. Сложности могут возникнуть, когда в нашем приложении есть некоторый класс, у которого есть множество наследников, и необходимо создавать экземпляр определенного класса в зависимости от некоторых условий. Фабрика — это шаблон проектирования, который помогает решить проблему создания различных объектов в зависимости от некоторых условий. Абстрактно, не правда ли? Больше конкретики и ясности появится, когда мы рассмотрим пример ниже.

Создаем различные виды кофе

Предположим, мы хотим автоматизировать кофейню. Нам необходимо научиться готовить различные виды кофе. Для этого в нашем приложении мы создадим класс кофе и его производные: американо, капучино, эспрессо, латте — те виды кофе, которые мы будем готовить. Начнем с общего кофейного класса:

public class Coffee {
    public void grindCoffee(){
        // перемалываем кофе
    }
    public void makeCoffee(){
        // делаем кофе
    }
    public void pourIntoCup(){
        // наливаем в чашку
    }
}
Далее создадим его наследников:

public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Наши клиенты будут заказывать какой-либо вид кофе, и эту информацию нужно передавать программе. Это можно сделать разными способами, например использовать String. Но лучше всего для этих целей подойдет enum. Создадим enum и определим в нем типы кофе, на которые мы принимаем заказы:

public enum CoffeeType {
    ESPRESSO,
    AMERICANO,
    CAFFE_LATTE,
    CAPPUCCINO
}
Отлично, теперь напишем код нашей кофейни:

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;
    }
}
Метод orderCoffee можно разделить на две составляющие:
  1. Создание конкретного экземпляра кофе в блоке switch-case. Именно здесь происходит то, что делает Фабрика — создание конкретного типа в зависимости от условий.
  2. Само приготовление — перемолка, приготовление и разлитие в чашку.
Что важно знать, если нужно будет вносить в метод изменения в будущем:
  1. Сам алгоритм приготовления (перемолка, приготовление и разлитие в чашку) останется неизменным (по крайней мере мы на это рассчитываем).
  2. А вот ассортимент кофе может измениться. Возможно, мы начнем готовить мока.. Мокка.. Моккачи… Господь с ним, новый вид кофе.
Мы уже сейчас можем предположить, что в будущем, с определенной долей вероятности, нам придется вносить изменения в метод, в блок switch-case. Также возможно, в нашей кофейне метод orderCoffee будет не единственным местом, в котором мы будем создавать различные виды кофе. Следовательно, вносить изменения придется в нескольких местах. Тебе уже наверняка понятно, к чему я клоню. Нам нужно рефакторить. Вынести блок, отвечающий за создание кофе, в отдельный класс по двум причинам:
  1. Мы сможем переиспользовать логику создания кофе в других местах.
  2. Если ассортимент изменится, нам не придется править код везде, где будет использоваться создание кофе. Достаточно будет изменить код только в одном месте.
Иными словами, пришло время запилить фабрику.

Пилим нашу первую фабрику

Для этого создадим новый класс, который будет отвечать только за создание нужных экземпляров классов кофе:

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;
    }
}
Поздравляю тебя! Мы только что реализовали шаблон проектирования Фабрика в самом его простейшем виде. Хотя все могло быть еще проще, если сделать метод createCoffee статичным. Но тогда мы потеряли бы две возможности:
  1. Наследоваться от SimpleCoffeeFactory и переопределять метод createCoffee.
  2. Внедрять нужную реализацию фабрики в наши классы.
Кстати о внедрении. Нам нужно вернуться в кофейню и внедрить нашу фабрику по созданию кофе.

Внедрение фабрики в кофейню

Перепишем класс нашей кофейни с использованием фабрики:

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;
    }
}
Отлично. Теперь схематично и лаконично попробуем описать структуру шаблона проектирования Фабрика.

5 шагов к открытию собственной фабрики

Шаг 1. У тебя в программе класс с несколькими потомками, как на картинке ниже: Паттерн проектирования Factory - 2Шаг 2. Ты создаешь enum, в котором определяешь enum-переменную для каждого класса-наследника:

    enum CatType {
        LION,
        TIGER,
        BARSIK
    }
Шаг 3. Ты строишь свою фабрику. Называешь её MyClassFactory, код ниже:

class CatFactory {}
Шаг 4. Ты создаешь в своей фабрике метод createMyClass, который принимает в себя переменную-enum MyClassType. Код ниже:

    class CatFactory {
        public Cat createCat(CatType type) {
            
        }
    }
Шаг 5. Ты пишешь в теле метода блок switch-case, в котором перебираешь все enum значения и создаешь экземпляр класса, соответствующий enum значению:

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.

Как тренироваться

Читать — хорошо, писать код —еще лучше. Если в твоем имени четное количество букв, попробуй создать свою виртуальную пиццерию. Если в твоем имени нечетное количество букв, попробуй создать виртуальный суши-бар. Если ты безымянный — тебе повезло. Сегодня можешь отдыхать.