Привет! Сегодня мы продолжим изучать паттерны проектирования и поговорим о фабричном методе (FactoryMethod).
Ты узнаешь, что это такое и для решения каких задач подходит данный шаблон. Мы рассмотрим этот паттерн проектирования на практике и изучим его структуру.
Чтобы все изложенное было тебе понятно, необходимо разбираться в следующих темах:
- Наследование в Java.
- Абстрактные методы и классы в Java.
Какую проблему решает фабричный метод
Во всех фабричных паттернах проектирования есть две группы участников — создатели (сами фабрики) и продукты (объекты, создаваемые фабриками). Представь ситуацию: у нас есть фабрика, выпускающая автомобили под маркой AutoRush. Она умеет создавать модели автомобилей с различными видами кузовов:- седаны
- универсалы
- купе
- седаны AutoRush
- универсалы AutoRush
- купе AutoRush
- седаны OneAuto
- универсалы OneAuto
- купе OneAuto
Немного о шаблоне фабрика
Напомню: мы построили с тобой небольшую виртуальную кофейню. В ней мы с помощью простой фабрики научились создавать различные виды кофе. Сегодня будем дорабатывать данный пример. Давай вспомним, как выглядела наша кофейня с простой фабрикой. У нас был класс кофе:
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 {}
Для удобства принятия заказов мы завели перечисления:
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
Сама фабрика по производству кофе выглядела следующим образом:
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;
}
}
Ну и, наконец, сама кофейня:
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;
}
}
Модернизация простой фабрики
Наша кофейня работает хорошо. Настолько, что мы подумываем о расширении. Мы хотим открыть несколько новых точек. Как предприимчивые ребята, мы не будем штамповать однообразные кофейни. Хочется, чтобы у каждой была изюминка. Поэтому для начала откроем две точки: в итальянском и американском стилях. Изменения затронут не только интерьер, но и напитки:- в итальянской кофейне мы будем использовать исключительно итальянские кофейные бренды, с особым помолом и прожаркой.
- в американской порции будут чуточку больше, и к каждому заказу будем подавать плавленный зефир — маршмеллоу.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
А станет 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 {}
Раз мы желаем сохранить действующую бизнес-модель неизменной, нам хочется, чтобы метод orderCoffee(CoffeeType type)
претерпел минимальное количество изменений.
Взглянем на него:
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
Какие варианты у нас есть? Мы ведь уже умеем писать фабрику? Самое простое, что сходу приходит в голову — написать две аналогичные фабрики, а затем передавать нужную реализацию в нашу кофейню в конструкторе. Тогда класс кофейни не изменится.
Для начала нам нужно создать новый класс-фабрику, унаследоваться от нашей простой фабрики и переопределить метод createCoffee (CoffeeType type)
. Напишем фабрики для создания кофе в итальянском и американском стилях:
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;
}
}
Теперь мы можем передавать нужную реализацию фабрики в CoffeeShop. Давай посмотрим, как бы выглядел код для заказа кофе из разных кофеен. Например, капучино в итальянском и американском стилях:
public class Main {
public static void main(String[] args) {
/*
Закажем капучино в итальянском стиле:
1. Создадим фабрику для приготовления итальянского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику итальянского кофе
3. Закажем наш кофе
*/
SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
/*
Закажем капучино в американском стиле
1. Создадим фабрику для приготовления американского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику американского кофе
3. Закажем наш кофе
*/
SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
}
}
Мы создали две различные кофейни, передав в каждую нужную фабрику. С одной стороны, мы достигли поставленной задачи, но с другой стороны... Что-то скребет неуемную душу предпринимателя… Давай разбираться, что не так.
Во-первых, обилие фабрик. Это что, каждый раз теперь под новую точку свою фабрику создавать и вдобавок следить за тем, чтобы при создании кофейни в конструктор передавалась нужная фабрика?
Во-вторых, это все еще простая фабрика. Просто немного модернизированная. Мы тут все-таки новый паттерн изучаем.
В-третьих, а что, нельзя что ли по-другому? Вот было бы классно, если бы мы могли локализовать все вопросы по приготовлению кофе внутри класса CoffeeShop
, связав процессы по созданию кофе и обслуживанию заказа, но при этом сохранив достаточную гибкость, чтобы делать кофе в различных стилях.
Ответ — да, можно. Это называется шаблон проектирования фабричный метод.
От простой фабрики к фабричному методу
Чтобы решить поставленную задачу максимально эффективно, мы:- Вернем метод
createCoffee(CoffeeType type)
в классCoffeeShop
. - Данный метод сделаем абстрактным.
- Сам класс
CoffeeShop
станет абстрактным. - У класса
CoffeeShop
появятся наследники.
CoffeeShop
, реализующий метод createCoffee(CoffeeType type)
в соответствии с лучшими традициями итальянских бариста.
Итак, по порядку.
Шаг 1. Сделаем класс Coffee
абстрактным. У нас появилось целых два семейства различных продуктов. У итальянских и американских кофейных напитков по-прежнему есть общий предок — класс Coffee
. Было бы правильно сделать его абстрактным:
public abstract class Coffee {
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
Шаг 2. Делаем CoffeeShop
абстрактным, с абстрактным методом createCoffee(CoffeeType type)
public abstract class CoffeeShop {
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = createCoffee(type);
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
protected abstract Coffee createCoffee(CoffeeType type);
}
Шаг 3. Создадим итальянскую кофейню, класс-потомок абстрактной кофейни. В нем мы реализуем метод createCoffee(CoffeeType type)
с учетом итальянской специфики.
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;
}
}
Шаг 4. Проделаем тоже самое, для кофейни в американском стиле
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;
}
}
Шаг 5. Взглянем на то, как будет выглядеть заказ латте в американском и итальянском стиле:
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);
}
}
Поздравляю тебя. Мы только что реализовали шаблон проектирования фабричный метод на примере нашей кофейни.
Принцип работы фабричного метода
Теперь рассмотрим подробнее, что же у нас получилось. На диаграмме ниже — получившиеся классы. Зеленые блоки — классы создатели, голубые — классы продукты. Какие выводы можно сделать?- Все продукты — реализации абстрактного класса
Coffee
. - Все создатели — реализации абстрактного класса
CoffeeShop
. - Мы наблюдаем две параллельные иерархии классов:
- Иерархия продуктов. Мы видим итальянских потомков и американских потомков
- Иерархия создателей. Мы видим итальянских потомков и американских потомков
- У суперкласса
CoffeeShop
нет информации о том, какая конкретно реализация продукта (Coffee
) будет создана. - Суперкласс
CoffeeShop
делегирует создание конкретного продукта своим потомкам. - Каждый потомок класса
CoffeeShop
реализует фабричный методcreateCoffee()
в соответствии со своей спецификой. Иными словами, внутри реализаций классов-создателей принимается решение о приготовлении конкретного продукта, исходя из специфики класса создателя.
Структура фабричного метода
На схеме выше представлена общая структура паттерна фабричный метод. Что еще здесь важно?- Класс Creator содержит реализации всех методов, взаимодействующих с продуктами, кроме фабричного метода.
- Абстрактный метод
factoryMethod()
должен быть реализован всеми потомками классаCreator
. - Класс
ConcreteCreator
реализует методfactoryMethod()
, непосредственно производящий продукт. - Данный класс отвечает за создание конкретных продуктов. Это единственный класс с информацией о создании этих продуктов.
- Все продукты должны реализовывать общий интерфейс — быть потомками общего класса-продукта. Это нужно, чтобы классы, использующие продукты, могли оперировать ими на уровне абстракций, а не конкретных реализаций.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ