Паттерн Builder (Строитель)
Паттерн Builder (Строитель) — это порождающий паттерн проектирования, который позволяет создавать сложные объекты пошагово. Он отделяет конструирование сложного объекта от его представления, так что в результате одного и того же процесса конструирования могут получаться разные представления.
Подробное описание
Паттерн Builder решает проблему «телескопического конструктора» (telescoping constructor anti-pattern), когда класс имеет множество параметров, часть из которых необязательна. Вместо передачи всех аргументов в один конструктор или использования множества перегруженных конструкторов, Builder предоставляет интерфейс для поэтапной настройки объекта.
Ключевая идея: разбить процесс создания объекта на последовательность шагов. Клиентский код вызывает методы строителя в нужном порядке, а затем запрашивает готовый объект. Это позволяет использовать один и тот же процесс строительства для создания разных представлений продукта.
Принцип работы
Структура паттерна
Паттерн состоит из следующих участников:
- Product — создаваемый сложный объект.
- Builder — абстрактный интерфейс, объявляющий шаги построения Product.
- ConcreteBuilder — конкретная реализация, которая собирает части продукта и предоставляет метод для получения результата.
- Director (опционально) — управляет порядком выполнения шагов построения. В современных реализациях на Python часто опускается в пользу Fluent Interface.
classDiagram
class Director {
+construct(builder)
}
class Builder {
<<interface>>
+buildPartA()
+buildPartB()
+getResult()
}
class ConcreteBuilder {
+buildPartA()
+buildPartB()
+getResult()
}
class Product {
+partA
+partB
}
Director --> Builder : uses
ConcreteBuilder ..|> Builder : implements
ConcreteBuilder --> Product : creates
Математическая/Логическая формулировка
Процесс построения можно описать как последовательность функций \(f_i\), применяемых к промежуточному состоянию объекта \(S\):
Где:
- \(S_{initial}\) — начальное состояние (пустой объект или прототип).
- \(f_i\) — метод строителя, устанавливающий конкретный параметр или часть объекта.
- \(S_{final}\) — полностью сконфигурированный объект.
Пример реализации на Python
Ниже представлен пример реализации паттерна Builder для создания конфигурации сервера. Используется подход Fluent Interface (возврат self), позволяющий выстраивать цепочки вызовов, что является идиоматичным для Python.
import json
from typing import Optional
class ServerConfig:
"""Product: Сложный объект конфигурации сервера."""
def __init__(self):
self.host: str = "localhost"
self.port: int = 8080
self.ssl_enabled: bool = False
self.max_connections: int = 100
self.database_url: Optional[str] = None
self.cache_ttl: int = 300
def __str__(self):
config_dict = {
"host": self.host,
"port": self.port,
"ssl_enabled": self.ssl_enabled,
"max_connections": self.max_connections,
"database_url": self.database_url,
"cache_ttl": self.cache_ttl
}
return json.dumps(config_dict, indent=4)
class ServerConfigBuilder:
"""ConcreteBuilder: Пошаговое создание конфигурации."""
def __init__(self):
# Инициализируем новый продукт для каждого билдера
self._config = ServerConfig()
def set_host(self, host: str) -> 'ServerConfigBuilder':
"""Устанавливает хост."""
self._config.host = host
return self
def set_port(self, port: int) -> 'ServerConfigBuilder':
"""Устанавливает порт."""
if not (1 <= port <= 65535):
raise ValueError("Port must be between 1 and 65535")
self._config.port = port
return self
def enable_ssl(self) -> 'ServerConfigBuilder':
"""Включает SSL шифрование."""
self._config.ssl_enabled = True
return self
def set_max_connections(self, count: int) -> 'ServerConfigBuilder':
"""Устанавливает максимальное количество соединений."""
if count < 1:
raise ValueError("Max connections must be positive")
self._config.max_connections = count
return self
def set_database(self, url: str) -> 'ServerConfigBuilder':
"""Подключает базу данных."""
self._config.database_url = url
return self
def set_cache_ttl(self, seconds: int) -> 'ServerConfigBuilder':
"""Настраивает время жизни кэша."""
self._config.cache_ttl = seconds
return self
def build(self) -> ServerConfig:
"""Возвращает финальный объект конфигурации."""
# Здесь можно добавить валидацию всего объекта перед отдачей
if self._config.ssl_enabled and self._config.port == 80:
# Пример логики валидации: обычно SSL не используют на 80 порту
pass
return self._config
if __name__ == "__main__":
# Пример 1: Базовая конфигурация
basic_config = ServerConfigBuilder().build()
print("Базовая конфигурация:")
print(basic_config)
print("-" * 20)
# Пример 2: Продвинутая конфигурация с цепочкой вызовов
prod_config = (ServerConfigBuilder()
.set_host("0.0.0.0")
.set_port(443)
.enable_ssl()
.set_max_connections(1000)
.set_database("postgres://user:pass@db/prod")
.set_cache_ttl(600)
.build())
print("Продакшн конфигурация:")
print(prod_config)
Достоинства и недостатки
Достоинства:
- Пошаговое создание. Позволяет конструировать объект поэтапно, откладывая некоторые шаги или выполняя их рекурсивно.
- Инкапсуляция сложности. Клиентский код изолирован от деталей создания сложного объекта.
- Принцип единственной ответственности. Логика создания отделена от логики использования объекта.
- Читаемость кода. Использование Fluent Interface делает код создания объектов понятным и похожим на естественный язык.
Недостатки:
- Усложнение структуры. Требует создания дополнительных классов (Builder), что может быть избыточно для простых объектов.
- Зависимость от конкретного билдера. Для создания разных вариантов объекта часто требуются разные конкретные билдеры.