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