Привет, друг! Сегодня мы продолжим изучать с тобой паттерны проектирования. В этой лекции будем говорить о Фабрике. Обсудим с тобой, какую проблему решают с помощью данного шаблона, посмотрим на примере, как фабрика помогает открывать кофейню. А еще я я дам тебе 5 простых шагов для создания фабрики.
Чтобы быть со всеми на одной волне и легко улавливать суть, тебе должны быть знакомы такие темы:
- Наследование в 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
можно разделить на две составляющие:
- Создание конкретного экземпляра кофе в блоке
switch-case
. Именно здесь происходит то, что делает Фабрика — создание конкретного типа в зависимости от условий. - Само приготовление — перемолка, приготовление и разлитие в чашку.
- Сам алгоритм приготовления (перемолка, приготовление и разлитие в чашку) останется неизменным (по крайней мере мы на это рассчитываем).
- А вот ассортимент кофе может измениться. Возможно, мы начнем готовить мока.. Мокка.. Моккачи… Господь с ним, новый вид кофе.
switch-case
.
Также возможно, в нашей кофейне метод orderCoffee
будет не единственным местом, в котором мы будем создавать различные виды кофе. Следовательно, вносить изменения придется в нескольких местах.
Тебе уже наверняка понятно, к чему я клоню. Нам нужно рефакторить. Вынести блок, отвечающий за создание кофе, в отдельный класс по двум причинам:
- Мы сможем переиспользовать логику создания кофе в других местах.
- Если ассортимент изменится, нам не придется править код везде, где будет использоваться создание кофе. Достаточно будет изменить код только в одном месте.
Пилим нашу первую фабрику
Для этого создадим новый класс, который будет отвечать только за создание нужных экземпляров классов кофе:
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
статичным. Но тогда мы потеряли бы две возможности:
- Наследоваться от
SimpleCoffeeFactory
и переопределять методcreateCoffee
. - Внедрять нужную реализацию фабрики в наши классы.
Внедрение фабрики в кофейню
Перепишем класс нашей кофейни с использованием фабрики:
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. У тебя в программе класс с несколькими потомками, как на картинке ниже: Шаг 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.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ