Вітання! Сьогодні ми продовжимо вивчати патерни проектування та поговоримо про фабричний метод (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()
, що безпосередньо виробляє продукт. - Цей клас відповідає за створення конкретних товарів. Це єдиний клас з інформацією про створення цих продуктів.
- Всі продукти повинні реалізовувати спільний інтерфейс – бути нащадками загального класу продукту. Це потрібно, щоб класи, які використовують продукти, могли оперувати ними лише на рівні абстракцій, а чи не конкретних реалізацій.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ