Factory Method
Factory Method — это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.
Проблема
Представьте, что вы создаете приложение для логистики. Первая версия может обрабатывать только перевозки грузов на грузовиках, поэтому весь код привязан к классу Truck.
Позже ваше приложение становится популярным, и вы получаете десятки запросов на добавление перевозок по морю. Отлично! Но что насчет кода? Большая часть существующего кода привязана к классу Truck. Добавление класса Shipпотребует изменений во всем коде.
Решение
Паттерн Factory Method предлагает создавать объекты не напрямую, используя оператор new, а через вызов особого фабричного метода. Не волнуйтесь: объекты всё равно будут создаваться через new, но вызываться он будет из фабричного метода.
Структура
Creator
├── factoryMethod(): Product
└── someOperation(): string
ConcreteCreator
└── factoryMethod(): ConcreteProduct
Product
└── operation(): string
ConcreteProduct
└── operation(): string
─ getInstance(): Singleton
Пример реализации
JavaScript
// Абстрактный класс создателя
class TransportCreator {
// Фабричный метод
createTransport() {
throw new Error('Метод createTransport должен быть переопределен');
}
// Бизнес-логика
planDelivery() {
const transport = this.createTransport();
return `Планируем доставку с помощью ${transport.deliver()}`;
}
}
// Конкретный создатель для грузовиков
class TruckCreator extends TransportCreator {
createTransport() {
return new Truck();
}
}
// Конкретный создатель для кораблей
class ShipCreator extends TransportCreator {
createTransport() {
return new Ship();
}
}
// Абстрактный продукт
class Transport {
deliver() {
throw new Error('Метод deliver должен быть переопределен');
}
}
// Конкретные продукты
class Truck extends Transport {
deliver() {
return 'грузовика по суше';
}
}
class Ship extends Transport {
deliver() {
return 'корабля по морю';
}
}
// Использование
function clientCode(creator) {
console.log(creator.planDelivery());
}
// Создаем доставку грузовиком
const truckCreator = new TruckCreator();
clientCode(truckCreator); // "Планируем доставку с помощью грузовика по суше"
// Создаем доставку кораблем
const shipCreator = new ShipCreator();
clientCode(shipCreator); // "Планируем доставку с помощью корабля по морю"
TypeScript с интерфейсами
// Интерфейс продукта
interface Animal {
makeSound(): string;
move(): string;
}
// Интерфейс создателя
interface AnimalFactory {
createAnimal(): Animal;
}
// Конкретные продукты
class Dog implements Animal {
makeSound(): string {
return 'Гав-гав!';
}
move(): string {
return 'Бегает на четырех лапах';
}
}
class Cat implements Animal {
makeSound(): string {
return 'Мяу-мяу!';
}
move(): string {
return 'Крадется бесшумно';
}
}
// Конкретные создатели
class DogFactory implements AnimalFactory {
createAnimal(): Animal {
return new Dog();
}
}
class CatFactory implements AnimalFactory {
createAnimal(): Animal {
return new Cat();
}
}
// Использование
function createAndInteract(factory: AnimalFactory) {
const animal = factory.createAnimal();
console.log(`Звук: ${animal.makeSound()}`);
console.log(`Движение: ${animal.move()}`);
}
const dogFactory = new DogFactory();
const catFactory = new CatFactory();
createAndInteract(dogFactory);
createAndInteract(catFactory);
Когда использовать
Используйте Factory Method, когда:
- Заранее неизвестно, объекты каких типов нужно создавать
- Класс делегирует создание объектов подклассам
- Нужно расширить систему новыми типами продуктов без изменения существующего кода
- Хотите предоставить библиотеку продуктов и раскрыть только их интерфейсы
Преимущества
- Избегает тесной связанности между создателем и конкретными продуктами
- Принцип единственной ответственности: создание продуктов вынесено в одно место
- Принцип открытости/закрытости: можно добавлять новые типы продуктов без изменения существующего кода
- Легко добавлять новые типы продуктов
Недостатки
- Код может стать более сложным из-за введения множества дополнительных классов
- Нужно создавать иерархию создателей для каждого типа продукта
Отличия от других паттернов
- Abstract Factory — создает семейства связанных объектов
- Builder — создает сложные объекты пошагово
- Prototype — создает объекты путем клонирования
Реальный пример
// Фабрика для создания UI компонентов
class UIComponentFactory {
createButton(type) {
switch(type) {
case 'primary':
return new PrimaryButton();
case 'secondary':
return new SecondaryButton();
case 'danger':
return new DangerButton();
default:
throw new Error(`Неизвестный тип кнопки: ${type}`);
}
}
}
class PrimaryButton {
render() {
return '<button class="btn btn-primary">Кнопка</button>';
}
}
class SecondaryButton {
render() {
return '<button class="btn btn-secondary">Кнопка</button>';
}
}
class DangerButton {
render() {
return '<button class="btn btn-danger">Кнопка</button>';
}
}
// Использование
const factory = new UIComponentFactory();
const primaryBtn = factory.createButton('primary');
console.log(primaryBtn.render());
Factory Method — это основа для создания гибких и расширяемых систем!