Швидше за все, до цього моменту ти вже стикався з патернами проєктування. Наприклад, з Одинаком (Singleton).

Давай пригадаємо, що таке патерни, навіщо вони потрібні, що таке породжувальні патерни (до яких належить Одинак), і вивчимо новий патерн — Фабричний метод.

Шаблон проєктування або патерн (design pattern) в розробці програмного забезпечення— це повторювана архітектурна конструкція, яка є вирішенням проблеми проєктування в рамках деякого контексту, що часто виникає.

Зазвичай шаблон не є завершеним зразком, який можна безпосередньо перетворити на код — це лише приклад вирішення завдання, який можна використовувати в різних ситуаціях.

Породжувальні шаблони (creational patterns) — шаблони проєктування, які мають справу із процесом створення об'єктів. Вони дозволяють зробити систему незалежною від способу створення, композиції та представлення об'єктів..

Фабричний метод (factory method) — це породжувальний патерн проєктування, який визначає загальний інтерфейс для створення об'єктів у батьківському класі, надаючи можливість створення цих об'єктів своїм спадкоємцям. На моменті створення спадкоємці можуть визначити, який клас створювати.

Яку проблему вирішує патерн?

Уяви, що ти вирішив створити програму доставки. Спочатку ти найматимеш кур'єрів з автомобілями, і в програмі як спосіб доставки будеш використовувати об'єкт Автомобіль. Кур'єри розвозять посилки з пункту А до пункту Б, В, тощо. Все просто.

Програма стає популярнішою, твій бізнес росте, ти хочеш масштабуватися, виходити на нові ринки. Так, наприклад, можна додатково почати доставляти їжу та займатися вантажними перевезеннями. Тоді їжу можуть доставляти і піші кур'єри, і на самокатах, і на велосипедах, а під важкі посилки потреби потрібні вантажні автомобілі.

Тепер тобі важливо знати, коли, кому, що і скільки конкретно буде доставлено з огляду на те, скільки кожен кур'єр може перевозити або переносити. У нових видів транспортних засобів різна швидкість та місткість. Тоді ти виявиш, що більшість сутностей у програмі сильно пов'язані з об'єктом Автомобіль, і щоб змусити твою програму працювати з іншими способами доставки, тобі доведеться переписати наявну кодову базу і робити це кожного разу для кожного нового транспорту.

У результаті виходить жахливий код, наповнений умовними операторами, які виконують ту чи іншу дію залежно від транспорту.

Вирішення проблеми

Патерн Фабричний метод пропонує створювати об'єкти не безпосередньо за допомогою оператора new, а через виклик особливого фабричного методу. Підкласи класу, що містить Фабричний метод, можуть змінювати об'єкти конкретних транспортних засобів, що створюються. На перший погляд це може здатися безглуздим: ми просто перемістили виклик конструктора з одного кінця програми до іншого. Але тепер ти зможеш перевизначати Фабричний метод у підкласі, щоб змінити тип транспорту, який ти створюєш.

Подивимося на діаграму класів для такого підходу:

Щоб ця система запрацювала, всі об'єкти, що повертаються, повинні мати спільний інтерфейс. Підкласи зможуть виробляти об'єкти різних класів, що наслідують той самий інтерфейс.

Наприклад, класи Вантажівка і Автомобіль реалізують інтерфейс Кур'єрський Транспорт з методом доставити. Кожен із цих класів реалізує метод по-своєму: вантажівки доставляють вантажі, а автомобілі — їжу, посилки тощо. Фабричний метод у класі Створювач вантажівок поверне об'єкт-вантажівку, а клас Створювач автомобілів — об'єкт-автомобіль.

Для клієнта фабричного методу нема різниці між цими об'єктами, оскільки він трактуватиме їх як певний абстрактний Кур'єрський Транспорт. Для нього буде важливо, щоб об'єкт мав метод доставити, а як він працює — не важливо.

Реалізація на Java:


public interface CourierTransport {
	void deliver();
}
public class Car  implements CourierTransport{
	@Override
	public void deliver() {
    		System.out.println("The parcel is delivered by car ");
	}
}
public class Truck implements CourierTransport{
	@Override
	public void deliver() {
    		System.out.println("Cargo is delivered by truck");
	}
}
public abstract class CourierTransportCreator {
	public abstract CourierTransport createTransport();
}
public class CarCreator extends CourierTransportCreator {
	@Override
	public CourierTransport createTransport() {
    		return new Car();
	}
}
public class TruckCreator extends CourierTransportCreator{
	@Override
	public CourierTransport createTransport() {
    		return new Truck();
	}
}
 
public class Deliver {
	private String address;
	private CourierTransport courierTransport;
 
	public Deliver() {
	}
 
	public Deliver(String address, CourierTransport courierTransport) {
    	this.address = address;
    	this.courierTransport = courierTransport;
	}
 
	public CourierTransport getCourierTransport() {
    		return courierTransport;
	}
 
	public void setCourierTransport(CourierTransport courierTransport) {
    		this.courierTransport = courierTransport;
	}
 
	public String getAddress() {
    		return address;
	}
 
	public void setAddress(String address) {
    		this.address = address;
	}
}
public static void main(String[] args) {
    	//приймаємо новий вид замовлення з бази (псевдокод)
    	String type = database.getTypeOfDeliver();
 
    	Deliver deliver = new Deliver();
    	
    	//заповнюємо транспорт в доставку
        deliver.setCourierTransport(getCourierTransportByType(type));
    	
    	//доставляємо
        deliver.getCourierTransport().deliver();
 
	}
 
	public static CourierTransport getCourierTransportByType(String type) {
    	switch (type) {
        	case "CarDeliver":
            	return new CarCreator().createTransport();
        	case "TruckDeliver":
            	return new TruckCreator().createTransport();
        	default:
            	throw new RuntimeException();
	    }
	}
    

Якщо ми захочемо створити новий об'єкт доставки, програма автоматично від її виду створить нам об'єкт транспорту.

Коли застосовувати патерн?

1. Коли заздалегідь невідомі типи та залежності об'єктів, з якими має працювати твій код.

Фабричний метод відокремлює код виробництва транспорту від решти коду, який використовує цей транспорт. Завдяки цьому код створення об'єктів можна розширювати без втручання до основного коду.

Так, щоб додати підтримку нового транспорту, тобі потрібно створити новий підклас і визначити фабричний метод, повертаючи звідти екземпляр нового транспорту..

2. Коли ти хочеш заощадити системні ресурси завдяки повторному використанню існуючих об'єкти замість створення нових.

Така проблема зазвичай виникає при роботі з важкими ресурсомісткими об'єктами, такими як підключення до бази даних, файлової системи тощо.

Уяви, скільки дій тобі потрібно зробити, щоб повторно використовувати існуючі об'єкти:

  1. Спочатку тобі слід створити спільне сховище, щоб зберігати в ньому всі об'єкти, що створюються.

  2. Під час запиту нового об'єкта потрібно буде зазирнути в сховище і перевірити, чи є там об'єкт, що не використовується.

  3. Повернути об'єкт клієнтському коду.

  4. Але якщо вільних об'єктів нема — створи новий, додати його до сховища.

Увесь цей код потрібно кудись розмістити, щоб не засмічувати клієнтський код. Найзручнішим місцем був би конструктор об'єкта, адже всі ці перевірки потрібні лише під час створення об'єктів. Але, на жаль, конструктор завжди створює нові об'єкти, він не може повернути існуючий екземпляр.

Отже, потрібен інший метод, який віддавав як існуючі, так і нові об'єкти. Ним і стане Фабричний метод.

3. Коли ти хочеш дати можливість користувачам розширювати частини твого фреймворку чи бібліотеки.

Користувачі можуть розширювати класи твого фреймворку через успадкування. Але як зробити так, щоб фреймворк створював об'єкти з цих нових класів, а не зі стандартних?

Рішенням буде дати користувачам можливість розширювати не тільки бажані компоненти, а й класи, які створюють ці компоненти. А для цього створюючі класи повинні мати конкретні створюючі методи, які можна визначити.

Переваги

  • Позбавляє клас від прив'язки до конкретних класів транспорту.
  • Виділяє код створення транспорту в одне місце, спрощуючи підтримку коду.
  • Спрощує додавання нових видів транспорту до програми.
  • Реалізує принцип відкритості/закритості.

Недоліки

Може призвести до створення великих паралельних ієрархій класів, оскільки для кожного класу продукту треба створити свій підклас створювача.

Підіб'ємо підсумок

Ти познайомився з патерном Фабричний метод та побачив його можливу реалізацію. Цей патерн досить часто використовується в різних бібліотеках, які в свою чергу надають об'єкти для створення об'єктів..

Використовуй патерн Фабричний метод у разі, коли хочеш без проблем впроваджувати у свою програму нові об'єкти-підкласи на основі вже наявних для взаємодії з основною бізнес-логікою, щоб не сильно роздувати код через різний контекст.


Вступна лекція в патерни/шаблони проєктування | Factory method pattern