Паттерн проектирования Factory Method (Фабричный метод)
Factory Method (Фабричный метод) — это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.
Подробное описание
Паттерн решает проблему жесткой привязки клиентского кода к конкретным классам продуктов. Вместо прямого вызова конструктора (new Class()), код обращается к специальному фабричному методу. Этот метод может быть переопределен в подклассах, что позволяет создавать разные типы объектов, не меняя код, который их использует.
Постановка задачи: Необходимо создать систему, которая должна работать с различными типами объектов (например, разными форматами документов или видами транспорта), но при этом не должна зависеть от конкретных реализаций этих объектов на этапе компиляции.
Ключевая идея: Делегирование ответственности за инстанцирование объектов от основного класса к его подклассам через переопределение специального метода.
Принцип работы
Структура паттерна
- Продукт (
Product) — объявляет интерфейс, который является общим для всех объектов, создаваемых фабричным методом. - Конкретные продукты (
ConcreteProduct) — различные реализации интерфейса продукта. - Создатель (
Creator) — объявляет фабричный метод, возвращающий объекты типаProduct. Может содержать базовую логику, зависящую от продукта. - Конкретные создатели (
ConcreteCreator) — переопределают фабричный метод так, чтобы он возвращал конкретный тип продукта.
Блок-схема взаимодействия
classDiagram
class Creator {
+factory_method() Product
+some_operation()
}
class ConcreteCreatorA {
+factory_method() Product
}
class ConcreteCreatorB {
+factory_method() Product
}
class Product {
<<interface>>
+operation()
}
class ConcreteProductA {
+operation()
}
class ConcreteProductB {
+operation()
}
Creator <|-- ConcreteCreatorA
Creator <|-- ConcreteCreatorB
Creator ..> Product : creates
Product <|.. ConcreteProductA
Product <|.. ConcreteProductB
ConcreteCreatorA ..> ConcreteProductA : creates
ConcreteCreatorB ..> ConcreteProductB : creates
Пример реализации на Python
Рассмотрим пример с логистической компанией, которая может использовать разные виды транспорта.
from abc import ABC, abstractmethod
# 1. Абстрактный продукт
class Transport(ABC):
"""Интерфейс для всех видов транспорта."""
@abstractmethod
def deliver(self) -> str:
pass
# 2. Конкретные продукты
class Truck(Transport):
def deliver(self) -> str:
return "Доставка груза грузовиком по земле"
class Ship(Transport):
def deliver(self) -> str:
return "Доставка груза кораблем по морю"
# 3. Абстрактный создатель
class Logistics(ABC):
"""Базовый класс логистики. Содержит бизнес-логику,
использующую фабричный метод для создания транспорта."""
@abstractmethod
def create_transport(self) -> Transport:
"""Фабричный метод, который должны реализовать подклассы."""
pass
def plan_delivery(self) -> str:
# Бизнес-логика не знает, какой именно транспорт будет создан
transport = self.create_transport()
return f"План доставки: {transport.deliver()}"
# 4. Конкретные создатели
class RoadLogistics(Logistics):
def create_transport(self) -> Transport:
return Truck()
class SeaLogistics(Logistics):
def create_transport(self) -> Transport:
return Ship()
# Клиентский код
def client_code(logistics: Logistics):
"""Клиент работает только с абстракцией Logistics."""
print(logistics.plan_delivery())
if __name__ == "__main__":
print("Наземная логистика:")
client_code(RoadLogistics())
print("\nМорская логистика:")
client_code(SeaLogistics())
Достоинства и недостатки
Достоинства:
- Избавляет от привязки к конкретным классам. Клиентский код работает с абстрактными типами, что упрощает замену реализаций.
- Принцип открытости/закрытости. Можно добавлять новые типы продуктов, создавая новые подклассы создателей, не изменяя существующий клиентский код.
- Инкапсуляция создания объектов. Логика создания сложного объекта скрыта внутри фабричного метода.
Недостатки:
- Усложнение кода. Требует введения множества новых подклассов для каждой вариации продукта.
- Может быть избыточным. Если нет необходимости в расширении типов продуктов, использование паттерна может привести к ненужному усложнению архитектуры по сравнению с простой функцией-фабрикой.